loading...

Stricter TypeScript compilation with Betterer

phenomnominal profile image Craig ☠️💀👻 ・5 min read

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 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... 🥶 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!

Abed from Community saying "Cool. Cool cool cool."

Cool. Cool cool cool.

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:

Phoebe from Friends getting her mind blown

Yes, it's that easy!

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:

Screenshot of Betterer terminal output for the first test run showing that the "stricter compilation" test has been run

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:

Screenshot of VS Code showing four issues in a TypeScript file.

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 by:

phenomnominal profile

Craig ☠️💀👻

@phenomnominal

Craig is a Software Engineer from New Zealand, working at Spotify in Stockholm. He loves building things that help teams build cool things! He also loves punk rock, Disney's Frozen, and his cat Cosy!

Discussion

markdown guide
 

love concept of the betterer :p

 

Thanks for making observable-webworker betterer Craig!