Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Controlling Type Checking Strictness in TypeScript

July 28, 2020
typescript

In this post, we will explore the compiler options that control the strictness of the type checking in TypeScript.

Understanding TypeScript strict mode

There is a TypeScript compiler option called strict. This turns on a set of type checking rules and is referred to as strict mode. This is separate from JavaScript’s strict mode.

When creating a new TypeScript project, it is recommended to have strict mode on so that code benefits from the most stringent type checking from the start of its life. However, strict mode may not be feasible when migrating a JavaScript codebase to TypeScript because of the number of type errors raised.

In addition to the strict option, each type checking rule can be controlled via a specific compiler option. This gives fine-grain control on the type checker’s strictness.

Understanding noImplicitAny

The noImplicitAny compiler option will raise errors in expressions and declarations with an implied any type. This option is true by default when the strict option is true.

The greet method below is currently violating this rule because the prefix parameter is inferred to have the any type:

public greet(prefix) {
  ...
}

We could set the noImplicitAny compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "noImplicitAny": false
  },
  ....
}

To properly resolve the error, we would give the prefix parameter a type annotation:

public greet(prefix: string) {
  ...
}

Understanding noImplicitThis

The noImplicitThis compiler option raises an error on expressions that reference this with an implied any type. This option is true by default when the strict option is true.

The goodbyeFunction method below is voilating this rule where the name property within this is accessed:

class Person {
  constructor(private name: string) {}

  goodbyeFunction() {
    return function() {
      console.log(`Goodbye ${this.name}`);
    };
  }
}

TypeScript infers this to be of type any and so raises a type error.

We could set the noImplicitThis compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "noImplicitThis": false
  },
  ....
}

There are two ways to resolve this error correctly.

The first method of resolving the type error is to let TypeScript know the type of this in the function signature:

goodbyeFunction() {
  return function (this: Person) {
    ...
  };
}

The second method of resolving the type error is to use an arrow function:

goodbyeFunction() {
  return () => {
    ...
  };
}

Nice!

Understanding strictBindCallApply

The strictBindCallApply compiler option enables stricter checking of the bind, call, and apply methods on functions. This option is true by default when the strict option is true.

The call to person.calculatePrice below is violating this rule because calculatePrice requires two arguments to be passed in. The function type is (discountCode: string, price: number) => number.

const discountedPrice = person.calculatePrice.apply(
  undefined,
  ["FREE"]
);

We could set the strictBindCallApply compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "strictBindCallApply": false
  },
  ....
}

To properly resolve the error we should pass the second parameter in the function call:

const discountedPrice = person.calculatePrice.apply(
  undefined,
  ["FREE", 100]
);

Understanding strictNullChecks

The strictNullChecks compiler option excludes the null and undefined values from the domain of every type. This option is true by default when the strict option is true.

The assignment of person to null in the code below is violating this rule because null is not within the Person type.

let person = new Person();
...
person = null;

We could set the strictNullChecks compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "strictNullChecks": false
  },
  ....
}

To better resolve this error we could add null to the type of person in a union type:

let person: Person | null = new Person();
...
person = null;

Understanding strictFunctionTypes

The strictFunctionTypes disables bivariant parameter checking for function types. This option is true by default when the strict option is true.

The getDiscountCode function below contains a type error because the info parameter contains a token property that is not part of APIRequestHandler:

type APIRequestHandler<T> = (info: { path: string }) => Promise<T>;

type APIRequestInfo = { path: string };
const getDiscountCode: APIRequestHandler<string> = async (
  info: APIRequestInfo & { token: string }
) => { ... }

We could set the strictFunctionTypes compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "strictFunctionTypes": false
  },
  ....
}

Resolving this type error isn’t straight forward. The getDiscountCode clearly requires the token property. We could change the APIRequestHandler type, but that might impact other code. A solution is to remove the type annotation from getDiscountCode and let TypeScript infer its type.

Understanding strictPropertyInitialization

The strictPropertyInitialization compiler option ensures non-undefined class properties are initialized in the constructor. This option requires strictNullChecks to be enabled to take effect. strictPropertyInitialization is true by default when the strict option is true.

The name property within the Person type below is currently violating this rule.

class Person {
  public name: string;
}

We could set the strictPropertyInitialization compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "strictPropertyInitialization": false
  },
  ....
}

We could assign a default value to name to properly resolve the error:

class Person {
  public name: string = "";
}

Understanding alwaysStrict

The alwaysStrict compiler option ensures JavaScript strict mode is used type checking process. It also determines whether "use strict" is emitted in JavaScript. This option is true by default when the strict option is true.

When alwaysStrict is on, "use strict" will be at the top of the transpiled JavaScript files:

"use strict";
class Person ...

In JavaScript strict mode, you can’t name a variable “arguments” like below:

let arguments;

We could set the alwaysStrict compiler option in tsconfig.json to false to remove the type error:

{
  "compilerOptions": {
    ...
    "alwaysStrict": false
  },
  ....
}

It is, however, advisable to correctly resolve JavaScript strict mode errors. In this case, we’d renamed the variable to something else:

let args;

Summary

TypeScript has a range of options for controlling how strict the type checking is:

  • strict - Sets maximum strictness, setting all the flags below to true.
  • noImplicitAny - Raises an error on expressions and declarations with an inferred type of any.
  • noImplicitThis - Raises an error on this expressions with an inferred type of any.
  • strictBindCallApply - Enables strict checking of the bind, call, and apply methods on functions.
  • strictNullChecks - Means that null and undefined values are not valid values in types.
  • strictFunctionTypes - Disables bivariant parameter checking for function types.
  • strictPropertyInitialization - Ensures class properties are assigned a default value or initialized in the constructor.
  • alwaysStrict - Parses the code in JavaScript strict mode and emits “use strict” in the transpiled code.

For new projects, it is recommended to set strict to true.

For JavaScript migration projects, strict can be set to true with the other flags to false if the corresponding rule raises lots of type errors. A goal could be to resolve the type errors and set all the strict flags to true over time.

If you to learn more about TypeScript, you may find my free TypeScript course useful:

Learn TypeScript

Learn TypeScript
Take a look
You might find some of my other posts interesting:
Fetch with async & await and TypeScript

Want more content like this?

Subscribe to receive notifications on new blog posts and courses

Required
© Carl Rippon
Privacy Policy