Most developers understand that JavaScript does not have a notion of datatypes: it’s often referred to as “dynamically typed”,  “weakly typed,” or even “untyped.”  The distinction is mostly academic, and the bottom line is when you declare a variable, you don’t specify its datatype.  For example, if you see this:

var animals;

There’s no way to know whether animals is an array, a string, a function, or something else.  So how do you make sure that people use variables as they were intended?

The first and most obvious solution is to document all your types.   If your program fits in one or two files, you can just check the documentation to determine any given type. But when your application spans dozens or hundreds of files, or the number of developers working on it begins to climb, this solution can quickly lead to a huge mess. When you get to this point, it’s helpful to offload “checking that function signature” to your IDE or text editor.

example of tooltips and type hinting in JavaScript using VisualStudio Code

If your IDE or editor doesn’t know that animals will eventually be an array, there’s no way for it to helpfully tell you that animals has the property length and the method map, among others. There’s no way for the IDE to know it’s an array… unless you tell it!  That’s where code hinting comes in.

On projects of any size, code hinting reduces typos, makes coding easier, and reduces the need to check documentation. Programmers who use strongly-typed languages such as Java and IDEs such as Eclipse take automated code-assistance for granted. But what about JavaScript programmers?

This post will examine a couple ways to clue-in your IDE to the types of the variables, function parameters, and return values in your program so the IDE can clue you in on how to use them. We’ll go over two ways to “tell” your IDE (and other developers) about types, and how to load type information for third-party libraries as well.

Before writing type annotations, however, you need a tool that can read them.

Setting up The Environment

The first thing you need is a code editor that recognizes and supports the concept of types in JavaScript. You can either use a JavaScript-oriented IDE such as Webstorm or VisualStudio Code, or if you have a favorite text editor, see if it has a type-hinting plugin that supports JavaScript. For example, both Sublime and Atom have type-hinting plugins.

I recommend Visual Studio Code (VS Code), for the following reasons:

  • It has built in code-hinting for JavaScript. No plugins needed.
  • It’s from Microsoft, which has ample experience creating IDEs.
  • Microsoft is also the creator of Typescript, so it has excellent support for Typescript definitions, one of the tools we’ll use herein.
  • It’s open source.
  • It’s free!

For these reasons, I’ll use VS Code in the remainder of this post.

With VS Code installed, let’s create a new project and get started!

Built-in Types

To begin an example of, use npm init to start a new JavaScript project. At this point, you already get quite a bit from VS Code, which has both JavaScript language APIs (Math, String, etc.) and browser APIs (DOM, Console, XMLHttpRequest, etc.) built in.

Here’s an illustration of some of what you get out of the box:

demo of type hinting for String, Math, Console, and Document

Nice! But we’re more interested in Node.js annotations and sadly, VS Code does not ship with those. Type declarations for Node.js core APIs do exist, however, in the form of Typescript declaration files. You just need a way to add them to your workspace so VS Code can find them: Enter Typings.

Typings

Typings is a “Typescript Definition Manager” that informs an IDE about the Typescript definitions (or “declarations”) for the JavaScript APIs you’re using. Later we’ll look at the format of Typescript declarations later; for now you need to inform the IDE about Node.js core APIs.

Install Typings:

$ npm install --global typings

With Typings installed on your system, you can add the Node.js core API type definitions to your project. From the project root, enter this command:

$ typings install dt~node --global --save

Let’s break that command down:

  1. Using the typings install command…
  2. Install the node package from dt~, the DefinitelyTyped repository, a huge collection of TypeScript definitions.
  3. Add the --global option to access to definitions for process and modules from throughout our project.
  4. Finally, the --save option causes typings save this type definition as a project dependency in typings.json, which you can check into your repo so others can install these same types. (typings.json is to typings install what package.json is to npm install.)

Now you have a new typings/ directory containing the newly-downloaded definitions, as well as your typings.json file.

One More Step…

You now have these type definitions in your project, and VS Code loads all type definitions in your project automatically. However, it identifies the root of a JavaScript project by the presence of a jsconfig.json file, and we don’t have one yet. VS Code can usually guess if your project is JavaScript based, and when it does it will display a little green lightbulb in the status bar, prompting you to create just such a jsconfig.json file. Click that button, save the file, start writing some Node, and…

demo of looking up core node.js api properties & methods using external node.js typescript definition file

It works! You now get “Intellisense” code hints for all Node.js core APIs. Your project won’t just be using Node core APIs though, you’ll be pulling in some utility libraries, starting with lodash. typings search lodash reveals that there’s a lodash definition from the npm source as well as global and dt. You want the npm version since you’ll be consuming lodash as module included with require('lodash') and it will not be globally available.

$ typings install --save npm~lodash
lodash@4.0.0
└── (No dependencies)

$ npm install --save lodash
typehinting-demo@1.0.0 /Users/sequoia/projects/typehinting-demo
└── lodash@4.13.1

Now you can require lodash and get coding:

example demonstrating property and method lookup of a application dependency (lodash)

So far you’ve seen how to install and consume types for Node and third party libraries, but you’re going to want these annotations for your own code as well. You can achieve this by using JSDoc comments, writing your own Typescript Declaration files, or a combination of both.

JSDoc Annotations

JSDoc is a tool that allows you to describe the parameters and return types of functions in JavaScript, as well as variables and constants. The main advantages of using JSDoc comments are:

  1. They’re lightweight and easy to use (just add comments to your code).
  2. The comments are human-readable, so they can be useful even if you’re reading the code on github or in a simple text editor.
  3. The syntax is similar to Javadoc and fairly intuitive.

JSDoc supports many annotations, but you can get a long way just by learning a few, namely <a href="http://usejsdoc.org/tags-param.html">@param</a> and <a href="http://usejsdoc.org/tags-returns.html">@return</a>. Let’s annotate this simple function, which checks whether one string contains another string:

function contains(input, search){
  return RegExp(search).test(input);
}

contains('Everybody loves types. It is known.', 'known'); // => true

With a function like this, it’s easy to forget the order of arguments or their types. Annotations to the rescue!

/**
 * Checks whether one string contains another string
 * 
 * <a class='bp-suggestions-mention' href='https://strongloop.com/members/param/' rel='nofollow'>@param</a> {string} input   - the string to test against
 * <a class='bp-suggestions-mention' href='https://strongloop.com/members/param/' rel='nofollow'>@param</a> {string} search  - the string to search for
 * 
 * @return {boolean}
 */
function contains(input, search){
  return RegExp(search).test(input);
}

Suppose that while writing this, you realize this function actually works with regular expressions as the search parameter as well as strings. Update that line to make clear that both types are supported:

/** 
 * ...
 * <a class='bp-suggestions-mention' href='https://strongloop.com/members/param/' rel='nofollow'>@param</a> {string|RegExp} search  - the string or pattern to search for
 * ...
 */

You can even add examples and links to documentation to help the next programmer out:

/**
 * Checks whether one string contains another string
 * 
 * @example
 * ```
 * contains("hello world", "world"); // true
 * ```
 * @example
 * ```
 * const exp = /l{2}/;
 * contains("hello world", exp);  // true
 * ```
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
 * 
 * <a class='bp-suggestions-mention' href='https://strongloop.com/members/param/' rel='nofollow'>@param</a> {string} input          - the string to test against
 * <a class='bp-suggestions-mention' href='https://strongloop.com/members/param/' rel='nofollow'>@param</a> {string|RegExp} search  - the string or pattern to search for
 * 
 * @return {boolean}
 */

…and away we go!

example of hinting function parameters & types based on JSDoc comments

JSDoc works great and you’ve only scratched the surface of what it can do, but for more complex tasks or cases where you’re documenting a data structure that exists for example in a configuration file, TypeScript declaration files are often a better choice.

Typescript Declarations

A TypeScript declaration file uses the extension .d.ts and describes the shape of an API, but does not contain the actual API implementation. In this way, they are very similar to the Java or PHP concept of an interface. If you were writing Typescript, you would declare the types of function parameters and so on right in the code, but JavaScript’s lack of types makes this impossible. The solution: declare the types in an JavaScript library in a Typescript (definition) file that can be installed alongside the JavaScript library. This is the reason you installed the Lodash type definitions separately from Lodash.

This post won’t cover setting up external type definitions for an API you wnat to publish and registering them on the typings repository, but you can read about it in the Typings documentation. For now, let’s consider the case of a complex configuration file.

Imagine you have an application that creates a map and allows users to add features to the map. You’ll be deploying these editable maps to different client sites, so you want be able to configure the types of features users can add and the coordinates to on which to center the map for each site.

The config.json looks like this:

{
  "siteName": "Strongloop",
  "introText":  {
    "title": "<h1> Yo </h1>",
    "body": "<strong>Welcome to StrongLoop!</strong>"
  },
  "mapbox": {
    "styleUrl": "mapbox://styles/test/ciolxdklf80000atmd1raqh0rs",
    "accessToken": "pk.10Ijoic2slkdklKLSDKJ083246ImEiOi9823426In0.pWHSxiy24bkSm1V2z-SAkA"
  },
  "coords": [73.153,142.621],
  "types": [
    {
      "name": "walk",
      "type": "path",
      "lineColor": "#F900FC",
      "icon": "test-icon-32.png"
    },
    {
      "name": "live",
      "type": "point",
      "question": "Where do you live?",
      "icon": "placeLive.png"
    }
    ...

You don’t want to have to read this complex JSON file each time you want to find the name of a key or remember the type of a property. Furthermore, it’s not possible to document this structure in the file itself because JSON does not allow comments.* Let’s create a Typescript declaration called config.d.ts to describe this configuration object, and put it in a directory in the project called types/.

declare namespace Demo{
  export interface MapConfig {
      /** Used as key to ID map in db  */
      siteName: string;
      mapbox: {
        /** @see https://www.mapbox.com/studio/ to create style */
        styleUrl: string;
        /** @see https://www.mapbox.com/mapbox.js/api/v2.4.0/api-access-tokens/ */
        accessToken: string;
      };
      /** @see https://www.mapbox.com/mapbox.js/api/v2.4.0/l-latlng/ */
      coords: Array<number>;
      types : Array<MapConfigFeature>;
  }

 interface MapConfigFeature {
    type : 'path' | 'point' | 'polygon';
    /** hex color */
    lineColor?: string;
    name : string;
    /** Name of icon.png file */
    icon: string;
  }  
}

You can read more in the Typescript docs about what all is going on here, but in short, this file:

  1. Declares the Demo namespace, so you don’t collide with some other MapConfig interface.
  2. Declares two interfaces, essentially schemas describing the structure and purpose of the JSON.
  3. Defines the types property of the first interface as an array whose members are MapConfigFeatures.
  4. Exports MapConfig so you can reference it from outside the file.

VS Code will load the file automatically because it’s in the project, and you’ll use the @type annotation to mark the conf object as a MapConfig when it’s loaded from disk:

/** @type {Demo.MapConfig} */
const conf = require('./config.js');

Now you can access properties of the configuration object and get the same code-completion, type information, and documentation hints! Note how in the following GIF, VS Code identifies not only that conf.types is an array, but when you call an .filter on it, knows that each element in the array is a MapConfigFeature type object:

example demonstrating looking up properties on an object based on a local Typescript Definition

I’ve been enjoying the benefits of JSDoc, Typescript Declarations, and the typings repository in my work. Hopefully this article will help you get started up and running with type hinting in JavaScript. If you have any questions or corrections, or if this post was useful to you, please let me know!