Start type checking your JavaScript without changing your code

22-Sep-2019

Index

Introduction

Now this post is not intended to persuade you to begin type checking your JavaScript. Rather if you're interested in type checking but don't want to transform your code to typescript yet, this article will inform you how to do it. A key benefit of this approach is that you can add type checking gradually, per file.

Enable JavaScript type checking

Install typescript
npm install -g typescript

  • Visual Studio Code can do the checks without needing to install typescript separately.
  • On other editors, you may need to configure the Typescript language server to include js files.
  • Otherwise, use the tsc cli: tsc filename.js --allowJs --checkJs --noEmit.

Type checking at project level

  • Create a jsconfig.json file at project root:
{
  "compilerOptions": {
    "checkJs": true
  }
}

This also allows you to specify other options

{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true, // Do not transpile (only type checking)
    "jsx": "react",
    "target": "es2016",
    "moduleResolution": "node",
    "lib": ["es2016", "dom"],
    "strict": true,
    "noImplicitAny": false,
    "baseUrl": "src"
  },
  "exclude": ["node_modules", "public"]
}

Type checking at per file

  • Add // @ts-check comment on top of the file you want to check

Adding types

Inferring types

Whenever possible, Typescript will infer the type of the variables automatically and provide relevant autocompletion and error reporting.

Autocompletion in VS Code

Showing errors in VS Code

Manually specifying types

You can specify types using JSDOC comments

On variables

/** @type {number | null} */
let a; // a will be assumed to be a number or null
/** @type {{ prop1: string, prop2: number[] }} */
let b; // Object b will have two properties prop1 which is a string and prop2 which would be a number array

On function parameters

/**
 * @param {Object} options - Object type
 * @param {string} options.prop1 - Property of Object options
 * @param {number} options.prop2 - Property of Object options
 * @param {string} arg1 - A string param.
 * @param {string} [arg2] - Optional param.
 * @param {string} [arg3="defaultValue"] - Optional param with default value
 * @return {string} return type
 */
function demo(options, arg1, arg2, arg3) {
  return arg1 + arg2 + arg3;
}

Define your own type

/**
 * @typedef {Object} MyObject - Define Object type MyObject
 * @property {string} prop1 - Property of Object MyObject
 * @property {number} prop2 - Property of Object MyObject
 */
/** @type {MyObject} */
let MyObject;

Importing type definitions

This is the really cool part. You can import type definitions form d.ts files and get type information for your npm packages.

/**
 * @typedef {import('webpack').Configuration} WebpackConfig
 * @typedef {import('webpack').RuleSetRule} RuleSetRule
 * @typedef {import('webpack').RuleSetLoader} RuleSetLoader
 */

/**
 * @type {WebpackConfig}
 */
const config = getConfig(); // config has the structure of the Webpack config object!
Demo

config object having properties of the webpack config object.

Importing types

The full list of JSDOC annotations

All available annotations and techniques can be read here:
Handbook: Type checking javascript files