Up your JavaScript autocomplete game using JSDocs

Note: I realize now that ${!isInSchool && "not"} will return "false" in some cases and not "" like I expected (I've been writing a lot of React Native code lately).. I swear I tested it and it didn't show false, but I just reran it and it did. O well, too many screenshots to fix it.

JavaScript is a great language, but it definitely has it's downfalls. One of the biggest complaints is that there is no strict-type checking, and that really starts to be noticed when autocomplete in systems like Intellisense have no idea what to suggest to you (especially in terms of writing functions with parameters).

Let's take a look at some code to understand the problem.

THE PROBLEM.

In this code above, let's see what happens when we type in the function name.

Notice how the Intellisense is saying the type for all of these parameters are "any"? While it's not a huge deal while typing in params, let's see what happens when we try to use a string function on the name param.

Thaaat.... doesn't help at all. Notice how there are no default string functions showing (things like toUpperCase, toLowerCase, etc). This is where JSDocs come in handy.

What are JSDocs?

JSDocs uses comments to annotate JavaScript. You can also add in extra descriptions and parameters (with types) to have Intellisense (or really any IDE) autocomplete.

JSDocs support is built into most IDEs such as VSCode, but if they aren't inherently supported there are 3rd party plugins to make it work.

How do I use JSDocs?

Writing JSDocs is as easy as pie! First, start the JSDocs syntax with /** and end it with */

The basic syntax goes as such:

/**
 * Description of function
 *
 * <tags>
 */

Make sure that the bullets after the first starting /** have a space before them! Now that we know how to write the basic form of them, let's explore tag types.

In the most basic form, JSDocs tags are:

  • @author - Developer's name
  • @constructor - Marks a function as a constructor
  • @deprecated - Marks a method as deprecated
  • @exception - Synonym for @throws
  • @module - Identifies a member that is exported by the module
  • @param - Documents a method parameter; a datatype indicator can be added between curly braces
  • @private - Signifies that a member is private
  • @returns - Documents a return value
  • @return - Synonym for @returns
  • @see - Documents an association to another object
  • @todo - Documents something that is missing/open
  • @this - Specifies the type of the object to which the keyword this refers within a function.
  • @throws - Documents an exception thrown by a method
  • @version - Provides the version number of a library

Source

That's a ton of information. In the end, the most useful ones will be @param, @return(s), and sometimes @module.

By adding  an @param tag, we are telling the IDE which type the variable should be (however do note: this is not doing actual strong type checking. It is just used for auto complete and docs). It can come in handy quite a bit, however, since it lets you choose from type methods.

With basic params setup, let's check out what name. will show now:

Ahh, so much better! Instead of just random things, the IDE shows useful methods for the parameter type. Rather nice.

When you start writing the function, the IDE will also show you the types (wahoo!) and the description of the function.

For documentation purposes, having @returns void is good to know so you don't try and return it.

It's almost like TypeScript.. but in normal JS ?

You may have noticed there is an @author tag. In my opinion, with how good git blames are, it's not totally necessary, but I totally see why a big team would use them for quick on-the-fly knowledge of who wrote what. For me, they don't add enough value to enforce.

Fun Fact: if you need to use an array as a param, you can use [] after the param name and it'll recommend array methods instead.

@param {string[]} stringList

Auto generating docs

You're probably thinking, "well, it seems pretty helpful, but what else can it do?" Well, if you install the npm package for jsdoc using npm install -g jsdoc, you can auto generate interactive HTML docs for your code!

Once you've installed the jsdoc npm package, you can run the command jsdoc test.js (or jsdoc -r . to create all files) and get a beautiful output in the out/ directory.

Auto generated output

If you go to the index.html page, you should see something that looks like the above, and if you click onto the "printInfo" section under "Global," you'll see something even cooler.

Wow! It shows the function, it's description, the parameters, types (along with description, if you put them @param {type} name - description), source, and expected return.

However, with this, there is a downside. If you are writing these for NodeJS and are using module.exports to export a function, it will still appear under "Global" even though it isn't. To fix this problem, let's add /** @module helpers */ to your file and rerun the command.

With that one module line, it now properly specifies where the functions belong. This is nice to know when you have multiple functions like in the example below:

A note on node_modules (or other directories you'd like to ignore)

If you had the pleasure of running jsdoc -r ., you'd quickly realize it was trying to build all of the docs for node_modules as well. We don't want that (usually). There is an easy fix: creating a jsdocs config file.

First, create a .jsdoc.js file and fill it with:

'use strict';

module.exports = {
    "source": {
        "exclude": [ "node_modules" ],
        "includePattern": ".+\\.js(doc|x)?$"
    }
}

Now, rerun the jsdoc command except as: jsdoc -r . -c .jsdoc.js. There we go, no more node modules :)

Conclusion

I just recently learned about JSDocs, but they'll definitely be in my back pocket from now on. In my opinion, the auto complete and extra detail you can provide is very useful as the readability is much better (also, being able to know which type a variable is meant to be is rather nice). If you want to become a JSDocs super user, you can also add ESLinting requirements to make sure they are written for everything. I have yet to make an ESLint config for it, but I probably will for some of my open source projects!