Type System

Because JavaScript has no compiler, types cannot be enforced by it. It can, however, be enforced by your IDE.

Built in Types

JavaScript has a whole list of primitive builtin types:

  • Boolean
  • Number
  • String
  • Object
  • Null
  • Undefined
  • Function
  • ...

It has also a couple of classes, that can be used the same way in JsDocs:

  • Array
  • Date
  • Error
  • Map
  • ...

JsDoc

JsDoc looks very similiar to JavaDoc. It has, on account of the missing compiler, a couple more features though. It can not only document but also define the input/output types or define entire "custom types".

This is a very basic definition:

/**
 * This is a basic description of a function.
 * @param { String } name - The name of the person.
 * @param { Number } age - The age of the person.
 * @returns { String } A greeting message.
 */
function greet(name) {
  return `Hello, ${name}! You are going to be ${age + 1} next year.`;
}

Common Tags

There are a lot of tags in JsDoc. Here are the most frequently used listed, a couple of them have more infos further below.

  • @function - Explicitly marks a block as a function, often for function expressions or anonymous functions.
  • @param - Describes a function parameter.
  • @template - Allows generic types, similar to TypeScript generics.
  • @returns - Describes what the function returns.
  • @type - Declares the type of a variable.
  • @typedef - Creates a custom type, like an object structure.
  • @property - Defines properties in a custom type.
  • @constructor - Indicates that a function is intended to be used as a constructor (for creating instances of an object).
  • @callback - Defines a function signature expected to be used as a callback type, specifying its parameters and return type.

Define Data Types

Union type: This is useful if the parameter can have multiple types.

/**
 * Get a string to describe the age of a person
 * @param { Number | String } age - The age of the person.
 * @returns { String } A greeting message.
 */
function age(age) {
  return `You are ${age} year(s) old!`;
}

Intersection type: Here the type must be a person who is also cool. This helps enact even more specific type checking.

/**
 * Log how cool a person is
 * @param { Person & Cool } person - A cool person.
 */
function cool(person) {
  console.log(`Hey, ${person.name} is really cool!`)
}

String literal type: If you want to enable a couple of different strings. Very much like enums in Java.

/**
 * Get a string to describe the greating of me or you
 * @param { "me" | "you" }  - arg Me or you
 * @returns { String } A greeting message.
 */
function age(arg) {
  return `Hello, you are ${arg}!`;
}

Capability: This can be used to describe what kind of capabilities an object should have, i.e. it should have a properties age that is of type number.

/**
 * Processes an object with properties `a` and `b`.
 * @param {{a: string, b: Function}} obj - The object to process.
 * @returns {void} 
 */
function processObject(obj) {
  console.log(`Value of a: ${obj.a}`); // We know a must be a string
  obj.b(); // Invoke the function
}

Define Interfaces

With the typedef tag, you can define your own data structures. These can then be used like types, i.e. @returns {Person}.

/**
 * @typedef {Object} Person
 * @property {string} name - The name of the person.
 * @property {number} age - The age of the person.
 * @property {Function} greet - A method that returns a greeting message.
 */

Generics

Using @template, you can use generics.

/**
 * @template T
 * @param {T[]} array - An array of elements of type T.
 * @returns {T} The first element of the array.
 */
function getFirstElement(array) {
  return array[0];
}

Functions: With this you can describe functions that use currying, in different degrees of precision.

The most simple way to describe something as a function, that takes A and B which produces C.

/**
 * A curried function that takes a string and returns another function.
 * The returned function then takes a number and returns a boolean.
 * @type { (string) => (number) => boolean }
 */
const isLongerThan = (str) => (length) => str.length > length;

To be a bit more precise, you can also name the parameters:

/**
 * A curried function that takes a number `a` and returns another function.
 * The returned function takes a number `b` and returns their sum as a string.
 * @type { (a:number) => (b:number) => string }
 */
const addAndStringify = (a) => (b) => String(a + b);

It is also possible to throw generics into this mix:

/**
 * A curried function that takes a value of any type `T` and returns another function.
 * The returned function takes a string and simply returns the original `T` value.
 * @type { <T> (a:T) => (message:string) => T }
 */
const echoAfterMessage = (a) => (message) => {
  console.log(message);
  return a;
};

Best Practices

If you are using types, or JsDoc in general, there are a couple of best practices you may want to follow:

  • Set up your IDE properly: it will make your life so much easier
  • Follow conventions: pick or define a convention and stick to it
  • Add examples: makes it so much easier to figure out how a function call should look
  • Link to documentation: use @link to link to relevant documentation
  • Validate the documentation: validate what you've documented with test cases