How to utilise JSDoc comment tags so that Visual Studio Code intellisense works great

Types in JavaScript

JavaScript is a loosely typed and dynamic language. Variables in JavaScript are not directly associated with any particular value type, and any variable can be assigned (and re-assigned) values of all types:

let foo = 42;    // foo is now a number
foo     = 'bar'; // foo is now a string
foo     = true;  // foo is now a boolean

Intellisense in VS Code

Visual Studio Code's intellisense will only work, if it understands the type of of your code.

In above example, after you write first line, let foo = 42; it will show you methods of a number:

But what if you assign a JSON, which is going to hold many properties like id, createdOn, etc.

It's also working fine. But, it's unlikely that your variable is going to hold values with initialization. So, now if you check for blank JSON, intellisense will stop working, because now VS code doesn't know the types.

Without proper intellisense, we often make typos, call the method which doesn't exist or even try to access the properties of an objects by a random guess.

To handle such and more complex scenarios, and make sure intellisense works right for those, we will use JSDoc's @param, @type and @typedef block tags.

JSDoc to the rescue

JSDoc comes with lots of tags, you can checkout them all on it's website: https://jsdoc.app/. But for this article, we are going to focus on below 3 tags:

@param

The @param tag provides the name, type, and description of a function parameter.

The @param tag requires you to specify the name of the parameter you are documenting. You can also include the parameter's type, enclosed in curly brackets, and a description of the parameter.

Let's look at some examples.

/**
 * @param {string} somebody
 */
function sayHello(somebody) {
    alert('Hello ' + somebody);
}

After above code, VS code's intellisense will work great whenever you try to call sayHello:

You can look at more examples at https://jsdoc.app/tags-param.html#examples.

@type

The @type tag allows you to provide a type expression identifying the type of value that a symbol may contain, or the type of value returned by a function. You can also include type expressions with many other JSDoc tags, such as the @param tag.

A type expression can include the JSDoc namepath to a symbol (for example, myNamespace.MyClass); a built-in JavaScript type (for example, string); or a combination of these. You can use any Google Closure Compiler type expression, as well as several other formats that are specific to JSDoc.

Let's take a look at example:

/** @type {Array} */
var foo;

For above code, typing foo. will load all Array's properties and methods:

@typedef

The @typedef tag is useful for documenting custom types, particularly if you wish to refer to them repeatedly. These types can then be used within other tags expecting a type, such as @type or @param.

This tag is really helpful, it helps us to shape any complex type. Let's take a look at example.

This example defines a more complex type, an object with several properties, and sets its namepath so it will be displayed along with the class that uses the type. Because the type definition is not actually exposed by the function, it is customary to document the type definition as an inner member.

// src/toast.js

/**
 * @typedef {Object} Toast
 * @property {string} id
 * @property {boolean} closed - Indicates whether user has close the toast.
 * @property {Date} generatedOn - Indicates when the toast was generated.
 * @property {string} message - toast content.
 * @property {"warn" | "info"} type -  Indicates type of toast.
 * Also useful to show different icons.
 */

/**
 * A function for showing toast
 * @param {Toast} toast - {@link toast} object
 * containing all components of the toast.
 */
export function showToast(toast) {}

Here is the breakdown of above code:

  1. The first line:
    1. We first indicated that we want to create a custom type using @typedef tag
    2. Then we indicated that it's going to be an Object. You can also create simpler custom type using primitive date types, for example string or number.
    3. And lastly, we named this type as Toast
  2. Now, as Toast is going to be an Object, in rest of the comments, we defined what are it's properties going to be using @property tag. You can learn more about @property tag here.

Now if you try to call showToast, VS code will do it's magic:

But, this is not enough. In practical scenarios, you would be generating Toasts in different files and calling showToast from there. You can export and import showToast in other files, but what about Toast type definition?

You can also import type definition the same way you import bindings from another module. But as types are created in comments, you need import them in comments:

// src/home.js

import { showToast } from "./toast";

/**
 * @returns {import("./toast").Toast[]}
 */
function getToasts() {}

const allToasts = getToasts();

allToasts.forEach((toast) => {
  showToast(toast);
});

Just to emphasis, here's how we imported Toast type definition:

/**
 * @returns {import("./toast").Toast[]}
 */

You can read more about @typedef at https://jsdoc.app/tags-typedef.html.

Conclusion

We learned how JSDoc block tags, @param, @type and @typedef can help us to achieve maximum out of VS Code's intellisense and code faster without getting into un-wanted issues.

That's it! Thanks for reading. Let me know your thoughts and feedbacks in comments section.

And yes, always believe in yourself 🌅

Photo by Joshua Earle on Unsplash

17