Stricter TypeScript compilation with Betterer
Craig ☠️💀👻
Posted on May 20, 2020
This week I announced the new release of ☀️ Betterer, 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 strict
mode.
Betterer TL;DR
Betterer is a test runner that helps make incremental improvements to your code! It is based upon Jest's 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 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 impliedany
type -
noImplicitThis
- raise error onthis
expressions with an impliedany
type -
alwaysStrict
- parse in strict mode and emit"use strict"
for each source file -
strictBindCallApply
- enable stricter checking of thebind
,call
, andapply
methods on functions -
strictNullChecks
- in strict null checking mode, thenull
andundefined
values are only assignable to themselves andany
-
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... 🥶 But that's where Betterer comes in!
@betterer/typescript
Betterer comes with a package called @betterer/typescript
, which enables running the TypeScript compiler with a modified version of an existing configuration:
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:
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!
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 if anyone has a project that they wanted to strictify:
And I got a reply 🎉:
Let's go through the process of adding @betterer/typescript
to 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, and then running the Initialise Betterer command:
Next, we need to add the @betterer/typescript
dependency and install everything:
yarn add @betterer/typescript -D
Creating the test
Now we can add our test in the brand new .betterer.ts
file:
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:
And we get a whole bunch of issues reported in the .betterer.results
file:
// 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 they even show up in the file in the editor:
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!
Now we can make a nice little pull request to the observable-webworker Github Repo 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! 🦄
Posted on May 20, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.