Stricter TypeScript compilation with Betterer
Tags: improvement, regression, typescript, compilers
Published: 2020-05-20 16:44:52

This week I announced the new release of [☀️ **Betterer**](https://dev.to/phenomnominal/betterer-v1-0-0-301b), and I'm still really excited about it! I'd like to walk you through a pretty powerful application of **Betterer** - incrementally adding [**TypeScript**'s](https://www.typescriptlang.org/) `strict` mode.

## **Betterer** TL;DR

[**Betterer**](https://github.com/phenomnomnominal/betterer) is a test runner that helps make incremental improvements to your code! It is based upon [**Jest**'s snapshot testing](https://jestjs.io/docs/en/snapshot-testing), but with a twist...

**Betterer** works in two stages. The first time it runs a test, it will take a snapshot of the result. From then on, it will compare the new result against that snapshot. If the test gets worse, that's a regression and **Betterer** will throw an error ❌. If the test gets better, we've made some progress and **Betterer** will update the snapshot ✅. That's pretty much it!

## What is strict mode?

TypeScript's [`strict` mode](https://www.typescriptlang.org/docs/handbook/compiler-options.html) turns on a more aggressive level of type-checking, sometimes jokingly referred to as Typescript's "hard mode"... 🥵

Turning on `strict` mode enables the following:

* `noImplicitAny` - raise error on expressions and declarations with an implied `any` type
* `noImplicitThis` - raise error on `this` expressions with an implied `any` type
* `alwaysStrict` - parse in strict mode and emit `"use strict"` for each source file
* `strictBindCallApply` - enable stricter checking of the `bind`, `call`, and `apply` methods on functions
* `strictNullChecks` - in strict null checking mode, the `null` and `undefined` values are only assignable to themselves and `any`
* `strictFunctionTypes` - disable bivariant parameter checking for function types
* `strictPropertyInitialization` - ensure non-undefined class properties are initialized in the constructor

These extra checks give you even more certainty about the behaviour of your code, but if you don't turn them on when you start a project it can be [_really tricky to enable them later_](https://www.lucidchart.com/techblog/2018/06/20/how-to-actually-improve-type-safety-with-the-typescript-strict-flags/)... 🥶 But that's where [**Betterer**](https://github.com/phenomnomnominal/betterer) comes in!

## `@betterer/typescript`

**Betterer** comes with a package called [`@betterer/typescript`](https://www.npmjs.com/package/@betterer/typescript), which enables running the **TypeScript** compiler with a modified version of an existing configuration:

```typescript
import { typescriptBetterer } from '@betterer/typescript';

export default {
  'consistent casing in file names': typescriptBetterer('./tsconfig.json', {
    forceConsistentCasingInFileNames: true
  })
};
```

As you can see, this test works with any of the **TypeScript** compiler options! But we want to enable hard mode, so we're going to use it like this:

```typescript
import { typescriptBetterer } from '@betterer/typescript';

export default {
  'stricter compilation': typescriptBetterer('./tsconfig.json', {
    strict: true
  })
};
```

This test will use **Betterer** to create a snapshot of the current state, and prevent us from regressing. Then we will fix the issues when we can and incrementally migrate to `strict` mode!

![Abed from Community saying "Cool. Cool cool cool."](https://media1.tenor.com/images/29a4af868d40abec096a65d91c7051a1/tenor.gif?itemid=5673616)
<figcaption>Cool. Cool cool cool.</figcaption>

## Finding a test subject...

So what does it look like with a real project? 🤔 I'm a big fan of `strict` mode, and I usually add it at the start of my projects! So instead I asked on [Twitter](https://twitter.com/phenomnominal/status/1262909583913181184) if anyone has a project that they wanted to strictify:

{% twitter 1262909583913181184 %}

And I got a reply 🎉:

{% twitter 1262913787453149184 %}

Let's go through the process of adding `@betterer/typescript` to [observable-webworker](https://github.com/cloudnc/observable-webworker).

## Setting up **Betterer**

First things first, we're going to need to enable **Betterer** in the project. We can do this by installing the [**Betterer** VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Betterer.betterer-vscode), and then running the Initialise Betterer command:

{% youtube U4zrxl7D2Xw %}

![Phoebe from Friends getting her mind blown](https://media.giphy.com/media/WtOdb75cMcjIK2jmTq/giphy.gif)
<figcaption>Yes, it's that easy!</figcaption>

Next, we need to add the `@betterer/typescript` dependency and install everything:

```bash
yarn add @betterer/typescript -D
```

## Creating the test

Now we can add our test in the brand new `.betterer.ts` file:

```typescript
import { typescriptBetterer } from '@betterer/typescript';

export default {
  'stricter compilation': typescriptBetterer('./tsconfig.json', {
    strict: true
  })
};
```

As a reminder, this test is going to run the **TypeScript** compiler with the existing project settings *and* the additional `strict: true` setting, and see how many issues there are...

The first time we run it, we get this:

![Screenshot of Betterer terminal output for the first test run showing that the "stricter compilation" test has been run](https://dev-to-uploads.s3.amazonaws.com/i/n8jwldtlnc6pjd0es8yx.png)

And we get a whole bunch of issues reported in the `.betterer.results` file:

```javascript
// BETTERER RESULTS V1.
exports[`stricter compilation`] = {
  timestamp: 1589981710157,
  value: `{
    "projects/observable-webworker/src/lib/from-worker-pool.ts:3842672600": [
      [46, 12, 18, "Type \'Worker\' is not assignable to type \'null\'.", "1459294796"],
      [47, 12, 12, "Type \'true\' is not assignable to type \'false\'.", "4249186060"],
      [49, 22, 13, "Property \'_cachedWorker\' does not exist on type \'LazyWorker\'.", "3268970724"],
      [53, 12, 18, "Object is possibly \'null\'.", "1459294796"]
    ],
    "projects/observable-webworker/src/lib/from-worker.ts:4009652202": [
      [28, 56, 11, "Argument of type \'Input | undefined\' is not assignable to parameter of type \'Input\'.\\n  Type \'undefined\' is not assignable to type \'Input\'.", "1574273654"]
    ],
    "projects/observable-webworker/src/lib/run-worker.ts:1913596105": [
      [4, 65, 7, "Rest parameter \'args\' implicitly has an \'any[]\' type.", "3622679692"],
      [51, 38, 26, "Cannot invoke an object which is possibly \'undefined\'.", "4028913799"],
      [51, 65, 18, "Argument of type \'O | undefined\' is not assignable to parameter of type \'O\'.\\n  Type \'undefined\' is not assignable to type \'O\'.", "3307950093"]
    ],
    ...,
    ...,
    ...,
    "src/readme/worker-pool-hash.worker.ts:2220989309": [
      [0, 21, 8, "Could not find a declaration file for module \'js-md5\'. \'./node_modules/js-md5/src/md5.js\' implicitly has an \'any\' type.\\n  Try \`npm install @types/js-md5\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'js-md5\';\`", "3516231053"]
    ]
  }`
};
```

That's great! There's quite a few issues there, but now we've got an established baseline and we're not going to introduce any new issues. And thanks to the [**Betterer** VS Code Extension](https://marketplace.visualstudio.com/items?itemName=Betterer.betterer-vscode) they even show up in the file in the editor:

![Screenshot of VS Code showing four issues in a TypeScript file.](https://dev-to-uploads.s3.amazonaws.com/i/jly3ex1s5n75cm7qvkdl.png)

We don't have time to fix them all right now, but while we're here we could fix a few! 😎

## Fixing some issues:

Let's go through and fix a few issues in a file, with the handy VS Code Diagnostics helping us out. And once we're done, we can run **Betterer** again to update the snapshot!

{% youtube m5qsDc3fDgo %}

Now we can make a [nice little pull request](https://github.com/cloudnc/observable-webworker/pull/41) to the [**observable-webworker** Github Repo](https://github.com/cloudnc/observable-webworker) and help them on their way to `strict` mode! 😍

## Tada! 🥳🥳🥳

That was a quick little walkthrough of one of the things **Betterer** can do - what do you reckon? Please let me know in the comments or hit me up on the Twitters! 🦄