New Feature: ts-expect-error

|10 min read|
Adam
Adam


Explore Other Articles from This Series

Introduction

TypeScript has been evolving steadily since 2012, the year when Microsoft announced its first public preview.

With the TypeScript 3.9 version released in May 2020, a new @ts-expect-error directive, among other features, has been made available to TypeScript developers. While having slight similarities to @ts-ignore, @ts-expect-error is more sophisticated and does additional code analysis that wasn't possible before.

If you are interested in knowing more about it, let's evaluate it together.

Prerequisites

  • NodeJS Long Time Support (Version 12.x)
  • NodeJS Package Manager (Version 6.x)

    • Yarn is also good, however, all examples here will use npm.
  • TypeScript (Versions: 3.9.x and 3.8.x)
  • Docker Engine 17.09.0+
  • GIT

These are soft requirements. If you have little time, you don't need to install any of them, nor follow the steps below. Feel free to explore the accompanying Github Repository instead or jump right to the Allowed Usage Variations Section further down.

Local Docker Environment Setup

To get the most out of this tutorial, we recommend setting up the local Docker environment, as outlined below. This will allow you to run multiple TypeScript Compiler's versions at the same time.

  1. Clone the accompanying repository

    git clone https://github.com/tekmi/typescript-evolution
  2. Build local Docker images for given TSC versions (each image weighs around 180-200 MB)

    cd typescript-evolution
    docker-compose build
  3. Run local Docker containers based on created tekmi/* images to verify the TSC versions

    docker run --rm tekmi/tsc39 tsc --version
    docker run --rm tekmi/tsc38 tsc --version
    
    // output at the time of writing
    Version 3.9.5
    Version 3.8.3

Having that, we are good to go. Testing @ts-expect-error directive with multiple TypeScript Compiler's versions should be a piece of cake now.

Examining "@ts-expect-error"

The previously cloned repository comes with a few simple examples, grouped under ts-expect-error directory.

├── Dockerfile
├── README.md
├── docker-compose.yml
└── ts-expect-error
    ├── helper.ts
    ├── package.json
    ├── unused.ts
    └── used.ts

The package.json file is fairly simple and has no packages to install. Instead, it comes with two scripts that alias lengthy docker commands:

  • tsc39 that creates the docker container based on the local tekmi/tsc39 image, including volume sharing and working directory setup
  • tsc38 that creates the docker container based on the local tekmi/tsc38 image, with volume sharing the working directory setup as well

The helper.ts file defines a simple function that accepts two arguments (num1 and num2). Each of these arguments must be of type number, as shown below:

export const iCanAddTwoNumbers = (num1: number, num2: number) => num1 + num2;

The used.ts and unused.ts files are the most important to us because we will be exercising the @ts-expect-error directive in there. Let's start with used.ts file and try to understand what is going on there:

import { iCanAddTwoNumbers } from './helper';

// @ts-expect-error
console.log(iCanAddTwoNumbers('notAllowed', 2));
  • We import the iCanAddTwoNumbers function from the helper.ts file.
  • We pass the notAllowed string as the first argument to the iCanAddTwoNumbers function which is obviously not allowed.
  • We use the @ts-expect-error directive in the comments to suppress TypeScript Compiler's errors.

To prove the TSC 3.9 Compiler is content, being in typescript-evolution/ts-expect-error directory, let's run the following command:

npm run -s tsc39 used.ts

No errors which mean TSC 3.9 has honored our new @ts-expect-error directive. If we run it with TSC 3.8 version though:

npm run -s tsc38 used.ts

we will immediately see the following error:

used.ts(4,31): error TS2345: Argument of type '"notAllowed"' is not assignable to parameter of type 'number'.

This proves that TSC 3.8 is not aware of the new @ts-expect-error directive, hence it reacts angrily by raising a compilation error.

Knowing that, let's continue with our second file, unused.ts that contains the following code:

import { iCanAddTwoNumbers } from './helper';

// @ts-expect-error
console.log(iCanAddTwoNumbers(1, 2));

It is almost the same as the used.ts file, the only difference is the type of the first argument passed to the iCanAddTwoNumbers function. Let's start with running unused.ts via TSC 3.8 Compiler as follows:

npm run -s tsc38 unused.ts

As expected, TSC 3.8 didn't report any errors. However, if we run it via TSC 3.9 Compiler as shown below:

npm run -s tsc39 unused.ts

we will notice this new, exotic error:

unused.ts(3,1): error TS2578: Unused '@ts-expect-error' directive.

This TS2578 error is a proof of the new suppression mechanism sitting behind the @ts-expect-error directive and working properly. We will come back to this later, but for now, let's summarize our findings of TSC 3.8 and TSC 3.9 Compilers in the table below:

TSC used.ts unused.ts
TSC 3.9 OK error TS2578
TSC 3.8 error TS2345 OK

Note: You could also use the TypeScript Playground to perform similar tests. However, you cannot import any files in there, which means all your code goes into one giant textarea. Additionally, you may not find the exact TypeScript Compiler's version you need because only a few are being emulated there. For example, at the time of writing, the latest TypeScript version is 3.9.5, while the TypeScript Playground supports only the maximum 3.9.2 version (plus the Nightly build).

At this point, we know that @ts-expect-error should be used inside the comments. Let's tinker with it a bit more and explore a few possible variations together.

Allowed Usage Variations

TypeScript 3.9 Compiler accepts the following scenarios:

  • it doesn't mind how many spaces are in front of the @ts-expect-error directive

    //        @ts-expect-error
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • it doesn't care if some spaces precede the comment with @ts-expect-error directive

               // @ts-expect-error
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • it doesn't bother if there is any text after the directive

    // @ts-expect-error random text here
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • it doesn't mind if empty lines sit in-between

    // @ts-expect-error
    
    
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • it doesn't care if there is another, one-line comment in-between

    // @ts-expect-error
    
    // another one-line comment
    
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • it permits one-line comments with /* */ notation

    /* @ts-expect-error */
    console.log(iCanAddTwoNumbers('notAllowed', 2));

Therefore, if you feel adventurous, you could write your code as shown below and TSC 3.9 Compiler would still understand you.

      /*      @ts-expect-error some text */

// another comment


console.log(iCanAddTwoNumbers('notAllowed', 2));

Disallowed Usage Variations

Knowing what is allowed, it's time to explore a few use cases where the TSC 3.9 Compiler would protest, such as:

  • comments with /* */ syntax that span to multiple lines

    /* @ts-expect-error
    */
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • the comment with @ts-ignore directive placed after the comment with @ts-expect-error directive

    // @ts-expect-error
    
    // @ts-ignore
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • the comment with /* */ syntax sitting in-between

    // @ts-expect-error
    
    /* hmm, not welcomed here... */
    
    console.log(iCanAddTwoNumbers('notAllowed', 2));
  • the @ts-expect-error directive put directly in the same line

    console.log(iCanAddTwoNumbers('notAllowed', 2)); // @ts-expect-error
  • any text placed before the @ts-expect-error directive

    // no text before me @ts-expect-error
    console.log(iCanAddTwoNumbers('notAllowed', 2));

After exploring the allowed and disallowed usage variations, let's move on to the last section that should serve as guidance on when to pick the @ts-expect-error directive over the @ts-ignore one.

"@ts-expect-error" or "@ts-ignore"

Both @ts-expect-error and @ts-ignore know how to soothe the TypeScript Compiler and can be used in the comments.

However, @ts-expect-error brings a new suppression mechanism to the table, as we briefly experienced before with the TS2578 error. Thanks to this mechanism, the TypeScript Compiler can monitor the correlated line of code and inform us whether the usage of @ts-expect-error directive still makes sense or not.

  • As soon as this line of code becomes valid and doesn't trouble the compiler anymore, it's time to remove the @ts-expect-error directive too.
  • At the time of writing, there is no way to define which errors to expect with @ts-expect-error, but some people were suggesting it under the original Pull Request

With that, you may be thinking to switch all @ts-ignore directives to @ts-expect-error, have a mix of them, or don't bother at all about it yet. As we don't want to bias you with our subjective view, we encourage you to read the recommendations from TypeScript maintainers.

The table below collects their thoughts and recommendations succinctly.

Pick "ts-expect-error" if Pick "ts-ignore" if
you’re writing test code where you actually want the type system to error on an operation you have an a larger project and and new errors have appeared in code with no clear owner
you expect a fix to be coming in fairly quickly and you just need a quick workaround you are in the middle of an upgrade between two different versions of TypeScript, and a line of code errors in one version but not another
you’re in a reasonably-sized project with a proactive team that wants to remove suppression comments as soon affected code is valid again you honestly don’t have the time to decide which of these options is better

Wrap-up

In this tutorial, we have looked into the new @ts-expect-error addition to the TypeScript 3.9 and covered the following aspects:

  • the preparation of the local Docker environment to run multiple versions of TypeScript Compiler
  • the comparison of TSC 3.8 and TSC 3.9 behaviors concerning the @ts-expect-error
  • the allowed and disallowed usage variations of @ts-expect-error
  • the guidance on when to pick the @ts-expect-error directive over @ts-ignore

Thanks to the evolution of TypeScript and its 3.9 release, we have just got another tool under our toolset. With the introduction of @ts-expect-error, we are sure you will be able to tackle your existing TypeScript problems with a new and fresh approach.