profile picture

JSDoc: utilizando um gerador de documentações como seu sistema de tipos

21 junho de 2021

react javascript typescript jsdoc programação

Há algum tempo venho utilizado o JSDoc para além de documentações técnicas em trechos de códigos. Muita gente não sabe, mas ele é extremamente poderoso e pode fazer muito mais do que você imagina. Então hoje abordarei um pouco mais a fundo sobre e também como utilizá-lo como sistema de tipos de seu projeto, não precisando migrar todo o projeto JavaScript para TypeScript para tipar seu código.

O que é o JSDoc?

O JSDoc é um projeto JavaScript criado com o propósito de documentar APIs de aplicações ou bibliotecas JavaScript. Existem muitas ferramentas assim fora do ambiente JS, como o JavaDoc no mundo Java, phpDocumentor para PHP e Doxygen para C++.

A notação de uma JSDoc é simples, sendo parecida com um comentário multi-linhas do JavaScript tendo apenas um * a mais (/** */):

/**
 * Função responsável por retornar um componente exibindo o
 * nome da pessoa usuária.
 *
 * @param {User} user
 * @returns {JSX.Element}
 */
function renderUserName({ name }) {
  return <Text>{name}</Text>;
}

Podemos ver então que criamos uma documentação básica, explicando o propósito a qual a função foi criada. Mas também percebe-se o seguinte, existem tipos declarados no parâmetro e no retorno da mesma. Isto é muito importante, pois é possível declarar tipos através de JSDocs, veja o exemplo do tipo User que usamos anteriormente:

/**
 * Tipo da entidade de Pessoa Usuária.
 *
 * @typedef User
 * @property {string} name - Nome da pessoa usuária
 * @property {string} birthdate - Data de nascimento da pessoa usuária
 * @property {string} birth - Data de nascimento da pessoa usuária
 */

VS Code + JSDoc = TypeScript

O Visual Studio Code possui uma ótima integração com JSDoc, possibilitando debugar tipos enquanto escreve-se códigos JavaScript. Isto porque a Intelissense do VSCode utiliza o serviço de JavaScript: Salsa, desenvolvido pelo time do TypeScript. Este serviço torna a Intelissense muito mais inteligente, possibilitando que além de documentar seu código JavaScript com as JSDoc, você pode também tipá-lo. Caso você você queira saber mais, vale a pena acessar a página que explica mais sobre o Salsa na documentação do TypeScript.

Tendo isto tudo posto, podemos percorrer algumas notações do JSDoc.

Notações do JSDocs

Como dito anteriormente, para declarar uma JSDoc basta utilizar o bloco de documentação /** */. Dentro dele você pode escrever um texto plano (descrevendo seu trecho de código) e também tags JSDoc. Começando a nossa lista pela notação @typedef.

@typedef

A tag @typedef é equivalente a uma declaração de type alias no TypeScript. Ou seja, esta tag é muito utilizada para declaração de tipos mais complexos e estruturas de múltiplos campos no seu código.

Veja o exemplo abaixo de um type alias em TypeScript:

type Person = {
  name: string;
  surname: string;
  age: number;
  gender?: boolean;
  weight: number;
  height: number;
};

O type alias acima declara ao todo seis propriedades diferentes para o tipo Person com os mais variados tipos, para utilizarmos este tipo em um código JavaScript podemos utilizar o @typedef aliado ao @property, veja a seguir:

/**
 * Tipo da entidade `Person`
 *
 * @typedef {Object} Person
 * @property {string} name
 * @property {string} surname
 * @property {number} age
 * @property {boolean} [gender]
 * @property {number} weight
 * @property {number} height
 */

Veja que a sintaxe muda um pouco, vindo os tipos antes dos nomes das propriedades. Além disto, para declarar tipos opcionais, como o caso de gender para o caso da pessoa não querer declarar seu gênero, podemos utilizar colchetes em volta do nome da propriedade para torná-la opcional.

Vale ressaltar que o @property tem seu diminutivo @prop, deixando mais simples a leitura da sua definição de tipo.

@type

A tag @type é usada para definirmos o tipo de uma expressão, ou seja, se quisermos utilizar o tipo Person declarado anteriormente, podemos apenas declarará-lo da seguinte forma:

/**
 * @type {Person}
 */
const person = {};

Para este caso poderíamos também utilizar a tag @constant para o caso de constantes.

/**
 * @constant
 * @type {Person}
 */
const person = {};

Desta forma, ao digitar Ctrl + Espaço veremos o seguinte:

Demonstração do Intelissense, exibindo as propriedades declaradas no tipo Person

Como demonstrado na imagem anterior, o Intelissense através do JSDoc consegue inferir tipos do JavaScript.

O @type também disponibiliza declarações inline, podendo declarar tipos que não serão reutilizados através da sintaxe TypeScript:

/**
 * @type {{ x: number, y: number }} Point
 */

@callback

Para casos onde é necessário declarar um tipo para uma função, o melhor a se fazer é utilizar o @callback junto às tags @param e @returns, que como o nome já diz são responsáveis, respectivamente, pelos tipos dos parâmetros e pelo retorno da sua função. O exemplo a seguir detalha uma função que contém um parâmetro único e um retorno:

/**
 * @callback FilterLegalAgePersons
 * @param {Person[]} persons
 * @returns {(Person & { fullName: string })[]}
 */

/** @type {FilterLegalAgePersons} */
const filterLegalAgePersons = (persons) =>
  persons
    .map(({ name, surname, ...person }) => ({
      fullName: `${name} ${surname}`,
      name,
      surname,
      ...person,
    }))
    .filter(({ age }) => age >= 18);

É possível também declarar funções curry, basta neste caso sua função retornar a funções separadamente, veja o exemplo:

/**
 * @param {BrowserHistory} history
 * @returns {(route: string) => void}
 */
const push = (history) => (route) => history.push(route);

Desta forma é possível ver que o tipo criado para a nossa função é o seguinte:

const push = (history: BrowserHistory) => (route: string) => void

Com o tipo declarado para nossas funções, a utilização destas fica muito mais simples. Além disto as deixamos documentadas facilitando a compreensão do código escrito.

@template

E por último, podemos declarar tipos genéricos através do JSDoc, para isto devemo utilizar a tag @template que é responsável por criar declarações de tipos genéricos, veja o exemplo a seguir em TypeScript:

function useState<T>(initialState: T) {
  let state = initialState;

  const setState = (newState: T) => {
    state = newState;
  };

  return [state, setState];
}

O código acima está em TypeScript, não existem tipos genéricos em JavaScript porém podemos declará-lo através de uma JSDoc, o exemplo a seguir demonstra este uso:

/**
 * Função utilitária criado com o propósito de controlar estados
 *
 * @template T
 * @param {T} initialState
 * @returns {[T, (newState: T) => void]}
 */
function useState(initialState) {
  let state = initialState;

  /**
   * Função responsável por definir novo estado.
   *
   * @param {T} newState
   */
  const setState = (newState) => {
    state = newState;
  };

  return [state, setState];
}

No exemplo é possível ver que a declaração de um @template pode ser reutilizado dentro de blocos de código diferentes.

Conclusão

Com todos estes exemplos é possível ver que não é necessário ter que realizar um processo migratório (e dependendo da sua base de código: complexo) para declarar e utilizar tipos em seu projeto. As JSDocs facilitam muito o trabalho de declarar tipos e documentações de funções, variáveis, constantes e o que precisar ser documentado em seu código JavaScript! Caso queira saber mais leia a documentação do TypeScript que descreve o JSDoc, além da própria documentação do JSDoc.

Agradeço pela sua atenção até aqui, não esqueça de compartilhar o post com seus amigos! Além disto minha DM está sempre aberta para discutirmos sobre no meu twitter @mechamobau. Obrigado pelos peixes e até próxima!