The power of jsDoc
A lot of developers complains about the weak-typing of Javascript (for good reasons). That's why we've seen the rise of Typescript.
But as neat as it is, Typescript come with some caveats.
- Hard typing
- Your code is parsed and changed
- Extra step to build
- New syntax to learn
Most of the time, these are easy to deal with or just ignore. Where jsDoc is really good, is that it reduce the pain of weak-type without any drawbacks and even add to the table.
What it is
First, let's see a complex example that we're going to reconstruct along this tutorial.
/**
* Nice little equine animal
* @class
* @extends Animal
*/
class Pony extends Animal {
/**
* Pony constructor
* @param {String} name - This pony's name
* @param {String} [color=Pony.colors.white] - This pony's color
*/
constructor (name, color = Pony.colors.white) {
super(name);
this.color = color;
/**
* This pony's age
* @type {Number}
*/
this.age = 0;
}
/**
* Make it run
* @param {...String} through - Any number of field to run through
* @example instance.run("field", "land");
* @return {Pony} Itself
*/
run (...through) {
console.log("Run through ", ...through);
return this;
}
/**
* @typedef {Object} PonyColors
* @prop {String} white - Pure light color
* @prop {String} black - Dark color
* @prop {String} foxy - Reddish color
*/
/**
* Get possible pony's colors
* @static
* @return {PonyColors}
*/
static get colors () {
return {
white: "#fff",
black: "#333",
foxy: "#c57432",
};
}
}
And, here's a demo of the advantages (using webstorm). Look closely at the autocomplete tooltips.
Now, let's go through it step by step.
Description
The easiest use of jsDoc is to describe a function or a class.
/**
* Nice little equine animal
*/
class Pony extends Animal {
/**
* Pony constructor
*/
constructor (name, color = Pony.colors.white) {
// ...
}
/**
* Make it run
*/
run (...through) {
// ...
}
/**
* Get possible pony's colors
*/
static get colors () {
// ...
}
}
Now, anyone using the code you wrote will have some information on the purpose of each functions or classes.
Depending on your IDE or editor, it will be shown in a tooltip whenever autocomplete kick-off.
Parameters
In the introduction, I talked about the variables types in JS. Here's where we'll tackle the problem.
JsDoc allow you to specify what parameters with what type (or types) are expect by a function. Then, your development environment should warn you if you give a parameter with incompatible type.
/**
* Pony constructor
* @param {String} name - A string
* @param {String|Number} color - A string or a number
*/
constructor (name, color = Pony.colors.white) {
// ...
}
In the same time, you can give a quick description of the variable use.
If a parameter is optional, just surround it with brackets and specify the default value if relevant.
/**
* Pony constructor
* @param {String} name - This pony's name
* @param {String} [color=Pony.colors.white] - This pony's color
*/
constructor (name, color = Pony.colors.white) {
// ...
}
We can see the effect, once again, on the autocomplete.
Type/callback definition
Sometimes, you could need to define custom types to describe some data you don't want to wrap in a class.
/**
* @typedef {Object} PonyColors
* @prop {String} white - Pure light color
* @prop {String} black - Dark color
* @prop {String} foxy - Reddish color
*/
That way, you can attach a type and a description to each item of an object.
The same is true for an expected parameter, consider the following:
/**
* @typedef {Object} CustomData
* @prop {String} name - Cute name for you pony
* @prop {String} [color=Pony.colors.white] - Color of its fur
* @prop {Number} [age=0] - Years since its birth
*/
/**
* Create a pony
* @param {CustomData} data
* @return Pony
*/
function ponyFactory (data) {
// ...
}
The CustomData
type is explained in a @typedef block. Type definition can event extends others with the @extends tag. This is really cool 8)
If a function expect a callback (typical array traverse for example), you can show what parameters will be passed to this callback.
/**
* @callback PonyCallback
* @param {Pony} pony - A pony
* @param {Number} index - Index of the pony
* @param {Array<Pony>} list - List of all the ponies
*/
/**
* Execute a function on each created pony
* @param {PonyCallback} callback - Function called on each pony
*/
function herd (callback) {
// ...
}
Further and beyond
The jsDoc documentation has a lot of tags for you to use. Each one allowing you to better inform the user of your code.
Honorable mention to @return (define the returned type), @abstract (this class should not be instantiate), @type (specify a type for any variable), @example (show an example of use) ...
Remember, most of the time, you will be the one to maintain your own code. So really, you're doing a service to yourself by documenting the code you write.
And last but not least, there are numerous ways to parse all the documentation and output fully formatted markdown to document your API for example.
Top comments (11)
jsDoc and TypeScript have interrelated goals. While jsDoc is mainly about providing context in the form of comments to functions / classes / etc, it also tries to act as a type system. The problem with jsDoc's pseudo typesystem being that you have to now actively maintain a codebase AND the corresponding type annotations. In a massive codebase this is a tough thing to do and requires a lot of discipline. Not to mention that the type annotations written in jsDoc have no guarantees that they're ever correct. For instance, you could have a jsDoc annotation that states that a function always returns a number. But perhaps in some edge cases your function coerces the number to a string. You don't have a compiler to help you in this case, so you have to write unit tests to assert that your function works in various scenarios (essentially taking the responsibility of a compiler). Or even worse (but happens often) the function behavior changes completely and the jsDoc type annotations are completely irrelevant (and probably counterproductive to the reader).
Alternatively, typescript's type annotations are always up to date. If you change your code and it conflicts with a type annotation in typescript, the compiler will let you know that you have a mismatch: "Hey your function no longer returns numbers 100% of the time". Well written types (especially with a healthy dose of type alias, eg.
type DateString = string
) are self-documenting. Maybe you can add a sentence or two to the functions on top of your types as well.Actually, when you're using VSCode, you can turn on TypeScript type checking for JavaScript with "implicitProjectConfig.checkJs": true". This tells the TypeScript language service to check the types for the JavaScript. When you add JSDoc comments, TypeScript uses those to understand the document's types. This means you get real time type checking in VSCode for plain ole JavaScript. This also includes features like symbol renaming across files, auto completion, go to definition, peek definition, go to type definition, find all references, etc. You can setup VSCode in settings for no explicit any for stricter type checking. If you try to change a type, VSCode will flag this error across all the files in your project where that type is used.
JSDoc lets you define custom types. You can import types from external files for reuse across your project. You can even do type casting. All of this while writing JavaScript. JSDocs with VSCode gives you type safety and intellisense for plain JavaScript. This means you're not limited to the JavaScript feature set that TypeScript supports. You can use whatever features currently supported by Babel, etc. If you've got a problematic line where type casting isn't working or you're using an expando property and don't want to escape it with [""], you can tell TypeScript to ignore it with:
// @ts-ignore
JSDoc comments are more verbose than the type declarations in TypeScript, but I personally prefer them because I know right where to look for a block of code's type information. The second benefit of JSDoc is that the comments can also be used to create HTML documentation for the code. In fact, the TypeScript team uses JSDoc comments in the TypeScript source code. JSDoc is the recommended way of providing documentation for TypeScript.
may not be 100% true anymore: github.com/Microsoft/tsdoc
Nice comment, laying a more TypeScript approach.
My point was mainly that jsDoc can step on TS shoes while adding context and description to your code.
On my personal projects, I never feel the need to use TS. While at work, however, I wish everyday that we had TS (and not just a bunch of outdated, badly formated jsDoc, like you describe)
I do agree that it can be overkill to use TS as well, and that JSDoc is great tool in many or most circumstances :)
I can see why people like TypeScript, but personally I don't like the overhead of working with it and rather write plain JS.
However, for expressing types and documentation I think TypeScript is a great tool.
I came up with the idea of using TypeScript typings solely for documentation, in a separate file like this. This gives you auto-completion and inline docs without disturbing the main coding workflow.
And you can use tools like typedoc to generate documentation.
I like this idea of a different file for the documentation, but is that not a pain in the a** to maintain ?
Not more than writing other kinds of external documentation.
I think it is a nice approach for packages with a small scope, but you are right, for internal-only code I would also prefer documentation inline with code since it is easier to think about updating it.
I find TypeScript much more readable than JSdoc: I don't have to look to a header to find out what type is intended, it's just there beneath the argument name. It breaks my reading flow far less.
Personally, I use and abuse of the autocomplete (as you certainly can tell from the article). So I never have to read the jsDoc to know intended types.
But of course, it depend of your editor and habits.
While coding, I use the autocomplete, too. However, if I have to solely read the code without changing it, for example when joining a project as a new developer, that won't help me.