Sintaxis avanzada de JSDoc

Aprende a añadir anotaciones más complejas con JSDoc


Una vez ya conocemos la sintaxis básica de JSDoc que vimos en el artículo anterior, podemos echar un vistazo a algunos detalles un poco más avanzados.

Eventos y callbacks

Al crear eventos y callbacks, algo muy frecuente en Javascript, es posible que los tipos de datos de sus componentes no se identifiquen correctamente en nuestro código. Para evitar esto, podemos utilizar las siguientes etiquetas de JSDoc:

EtiquetaDescripciónAlias
@callbackDefine una función que se pasará por parámetro como callback.-
@namespaceIndica que estamos en un espacio de nombres para organizar información.-
@eventIndica un evento que se puede emitir.-

Observa el siguiente ejemplo donde tenemos una función clickEmitter que espera una función como parámetro. En su interior, usamos un temporizador para ejecutar una nueva función cuando pase 1 segundo, que ejecute la función callback pasada por parámetro, pasándole el mensaje Click!.

En esta función se le añaden las etiquetas de JSDoc como hemos visto hasta ahora. El cambio está en como se comenta al utilizar la función clickEmitter y no al declararla:

/**
 * Emite un evento de "click".
 * @param {clickCallback} callback Función a ejecutar cuando se produce el click.
 */
function clickEmitter(callback) {
  setTimeout(() => callback("Click!"), 1000);
}

/**
 * Función de devolución de llamada para el evento de click.
 * @callback clickCallback
 * @param {string} mensaje Mensaje a mostrar.
 */
clickEmitter((message) => console.log(message));

Observa que lo que hacemos es definir la etiqueta @callback, ponerle un nombre y definir lo que esperamos que reciba.

Por otro lado, podemos utilizar la etiqueta @event cuando estemos trabajando con emisores de eventos y tengamos que emitir o recibir alguno.

Por ejemplo, observa este ejemplo donde creamos un objeto DataEmitter que usaremos como espacio de nombres para organizar métodos que gestionan eventos. Con la etiqueta @namespace de JSDoc la documentamos.

Luego, dentro del objeto, utilizamos @event para avisar que ese objeto, clase o estructura tiene la capacidad de emitir ciertos eventos, indicandole su tipo y los datos que emite:

/**
 * Espacio de nombres para gestionar eventos relacionados con datos.
 * @namespace DataEmitter
 */
const DataEmitter = {
  /**
   * Emite un evento personalizado `read` cuando el usuario termina de leer un post.
   * @event DataEmitter#read
   * @type {Object}
   * @property {string} post El nombre del post leído
   */

  /**
   * Función que emite el evento
   * @param {string} post El nombre del post
   */
  markAsRead(post) {
    const event = new CustomEvent("read", { detail: { post } })
    document.dispatchEvent(event);
  }
}

DataEmitter.markAsRead("introduccion-a-jsdoc");

Finalmente, documentamos el método como una función normal, a las que estamos acostumbrados.

Union types y enumeraciones

Mediante JSDoc podemos definir los tipos de una variable, limitando a un único tipo de dato. Sin embargo, puede que en nuestro caso queramos algo menos rígido. Tenemos varias opciones.

La unión de tipos se puede utilizar en JSDoc para limitar los valores que puede tomar una variable, pero manteniendo cierta flexibilidad.

Por ejemplo, imagina que quieres crear variables que puedan contener uno de los días de la semana. Te interesa que se puedan escribir días como Monday o Tuesday, pero que no puedas escribir Paco. Si definimos el tipo como string estamos permitiendo cualquier cadena de texto.

Con la sintaxis de las union types podemos ser más específicos. Simplemente se basa en separar las opciones mediante pipes |. En este ejemplo lo he separado en varias lineas por legibilidad:

/**
 * @typedef { 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday'
 *          | 'Saturday' | 'Sunday' } DayOfWeek
 */

/** @type {DayOfWeek} */
const day = "Sunday"

De hecho, en nuestro editor, al crear la constante day e intentar asignarle un valor, el autocompletado nos proporcionará los valores que hemos puesto en la parte superior.

Ten en cuenta que las uniones permiten también uniones avanzadas, como definir {Array<string|number>} para permitir un array donde sus elementos sean string o number.

Por otro lado tenemos las enumeraciones. Son muy similares a las union types, pero son más específicas y se utilizan para describir un conjunto de posibilidades, opciones o estados dentro de un cierto contexto.

EtiquetaDescripción
@enumDefine una enumeración, con varios valores.

Veamos un ejemplo utilizando @enum. Hemos creado un objeto Day que contiene una serie propiedades con un valor que lo identifica (en este caso un texto, aunque podría ser un número u otro valor):

/** @enum {string} */
const Day = {
  MONDAY: 'Monday',
  TUESDAY: 'Tuesday',
  WEDNESDAY: 'Wednesday',
  THURSDAY: 'Thursday',
  FRIDAY: 'Friday',
  SATURDAY: 'Saturday',
  SUNDAY: 'Sunday'
};

/** @typedef {keyof typeof Day} WeekDay */
/** @type {WeekDay} */
const today = "MONDAY";

Un poco más abajo, establecemos un tipo con @typedef llamado WeekDay que es la key del objeto definido anteriormente (keyof typeof Day). A continuación, creamos una constante today a la que le decimos que su tipo es una de las keys de Day (o sea, WeekDay).

Si todo ha ido bien en cualquiera de las dos formas, el editor nos debería mostrar como opciones los días de la semana en el autocompletado.

Genéricos

Los genéricos son una forma de documentar funciones mediante plantillas que nos permiten que los tipos sean más flexibles y reutilizables. Para ello, utilizaremos la etiqueta @template de JSDoc y le pondremos un nombre al genérico, normalmente una letra en mayúsculas.

EtiquetaDescripción
@templateDefine un genérico (plantillas). Son estructuras que pueden tener tipos diferentes.

Observa este ejemplo de una función que devuelve el primer elemento de un array:

/**
 * Función genérica que devuelve el primer elemento de un array.
 * @template T
 * @param {Array<T>} array Array de donde se extraerá el elemento.
 * @returns {T | undefined} El primer elemento del array si no está vacío.
 */
function firstElement(array) {
  return array[0];
}
  • 1️⃣ Con @template elegimos un nombre para el genérico. En este caso, la letra T, pero podría ser otra.
  • 2️⃣ En el parámetro usamos Array<T>. Indica que los arrays tienen elementos (genéricos) de tipo T.
  • 3️⃣ La función devuelve elementos de tipo T o undefined si está vacía.

Extender clases

Es muy posible que en nuestros fragmentos de código queramos extender clases con funcionalidad heredada, de modo que podamos reutilizar fragmentos de código facilmente. En JSDoc tenemos a nuestra disposición la etiqueta @extends, con la que podemos indicar que una clase extiende de otra, heredando sus propiedades y características.

Observa este ejemplo donde definimos la clase EventBase y luego creamos la clase Button que extiende de la anterior. En general

/**
 * Clase base que proporciona funcionalidades de registro de eventos.
 * @class
 */
class EventBase {
  // ...
  on(event, listener) { /* ... */ }
  emit(event, ...args) { /* ... */ }
}

/**
 * Clase que representa un botón.
 * @class
 * @extends EventBase
 */
class Button extends EventBase {
  // ...
  click() { /* ... */ }
}

// Ejemplo de uso:
const button = new Button('Enviar');

Por lo habitual, no suele hacer falta documentar esto con JSDoc para tener un autocompletado adecuado, pero si queremos añadirlo a nuestra documentación automática, nos puede venir muy bien.

Otro detalle interesante, muy relacionado a esto, es la posibilidad de utilizar la etiqueta @mixin para documentar mixins de nuestro código, es decir, porciones de código que están construidas para compartirse y «mezclarse» con otros elementos.

Otras etiquetas

Existen muchas otras etiquetas adicionales para JSDoc, entre las que se encuentran algunas como las siguientes:

EtiquetaDescripción
@moduleIndica que el archivo pertenece a un módulo concreto.
@asyncIndica que la función es una función asíncrona.
@seeHace referencia a otras partes relacionadas del código.
@linkEtiqueta en linea para hacer referencia a un elemento: {@link name}.
@exampleEstablece un ejemplo de código para añadir contexto.
@throwsIndica que puede lanzar una excepción.
@deprecatedIndica que el elemento está marcado como obsoleto y no debe usarse.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev