María Simó Front—end developer

Guía de ESLint, parte 1: cómo usar ESLint con confianza

ESLint es una herramienta con la que convive cualquier persona que desarrolla en el ecosistema de Javascript. Pero, ¿realmente la conocemos? Revisamos a fondo el sistema actual de configuración ESLint antes de migrar a flat config, su nuevo sistema.

Scroll to Content

Created 8 months ago

Updated 8 months ago

Status

Draft, I'm still learning about this

Motivaciones y plan para esta guía

El objetivo de esta serie de artículos es explicar cómo compartir nuestra configuración de ESLint como una dependencia externa para automatizar los estándares de código del equipo de front de Z1 Digital Studio.

Al comenzar a investigar cómo hacer esto, descubrí que ESLint está en el proceso de lanzar un nuevo sistema de configuración llamado flat config (que se traduciría en algo así como "configuración plana"). Este sistema ya es funcional, tiene soporte en la CLI y documentación oficial disponible desde la versión 8.23.0. Viene a sustituir a eslintrc (en adelante el sistema legacy o tradicional), que perderá soporte a partir de la versión 9. En este enlace puedes consultar del proceso de implementación.

Flat config propone cambios drásticos en la manera en la que configuramos ESLint en los proyectos. Por ello, merece la pena hacer una pequeña disgresión para aprender sobre la nueva configuración antes de lanzarnos a crear nuestra dependencia externa. Así podemos liderar su adopción y evitar refactorizar cuando el cambio sea efectivo.

Este artículo asume que has usado ESLint con anterioridad, aunque quizás no hayas entrado en el detalle de cómo funciona o todo lo que puede ofrecer.

El plan para esta serie de artículos es el siguiente:

Parte 1. Dominando ESLint. Primero aprenderemos todo lo necesario del sistema legacy para sacar el mayor partido del proceso de migración. Así podemos usar ESLint con confianza y control.

Parte 2. Migrando a flat config. Descubrimos los cambios esenciales que propone la flat config, y migramos nuestro caso práctico al nuevo sistema.

Parte 3. Creando una shareable config de ESLint. Profundizamos en las shareable configs y el ecosistema de dependencias de ESLint. Incorporamos otras herramientas de análisis estático. Empezamos a configurar nuestro repositorio como dependencia NPM.

Parte 4. Mejorando la experiencia con herramientas adicionales. Añadimos gestión de versiones y de dependencias. Creamos un README para documentar y facilitar el uso de nuestra dependencia. Exploramos la creación de una CLI para complementarla.


Qué es ESLint y porqué es importante

ESLint es una herramienta de análisis estático de código. Frente a las herramientas de análisis dinámico, como el testing, que necesita ejecutar el código para darnos un resultado, ESLint es capaz de analizar nuestro código sin ejecutarlo. De forma que nos ayuda a mantener y mejorar la calidad del código que escribimos al tiempo que lo escribimos.

ESLint automatiza nuestras opiniones en base a reglas, y nos advierte cuando una de estas reglas se incumple.

Es la herramienta más popular en su categoría, que incluye otras como Prettier, StyleLint, CommitLint... o el type checker de Typescript. Vamos a configurar estas herramientas en el inicio del proyecto y nos van a asistir de manera continuada durante su desarrollo. Lo ideal es ejecutarlas en diferentes fase del proceso (en el IDE, al hacer commit, en nuestro pipeline de integración continua...), para asegurarnos de que cumplimos con los estándares de calidad que hemos establecido.

Y, ¿de qué manera nos ayuda ESLint a crear y mantener estándares de calidad? Lo primero es que no toma decisiones por nosotros, sino que deja de nuestra mano, de la mano del equipo, convenir en qué va a definir la calidad del código. Automatiza nuestras opiniones en base a reglas, y nos advierte cuando una de estas reglas se incumple.

Todo esto se expresa en uno o más archivos de configuración, donde declaramos las reglas que van a aplicar al proyecto. Normalmente también nos vamos a apoyar en una extensión de ESLint para nuestro IDE, para obtener un feedback inmediato. Sin esta extensión, tendríamos que confiar únicamente en la CLI de ESLint para la revisión del código.

El valor que obtenemos de ESLint depende en gran medida del esfuerzo que invertimos en entenderlo. Muchas veces se adopta por inercia, trasladando de manera ciega las mismas configuraciones de un proyecto a otro, sin control sobre qué dicen esas reglas sobre el diseño de nuestro proyecto.

En los peores casos, ESLint se puede convertir en un enemigo que nos grita y no entendemos porqué: "si mi código funciona, ¿de qué se queja ESLint ahora?". Entonces se comporta como una sobrecarga de configuraciones que no sabemos cómo manejar y que nos hará odiar la amalgama de líneas onduladas rojas y amarillas que campa a sus anchas por los archivos del proyecto.

Pero cuando lo usamos de la manera correcta, ESLint es un super poder. Nos ayuda a mantener una consistencia a lo largo de la base de código y durante toda la vida de la aplicación, mejorando su diseño y mantenibilidad.

Si, una y otra vez, nos encontramos corrigiendo o comentando un error recurrente con el equipo, es probable que exista una regla de ESLint que podemos añadir para automatizar la solución. Las mejores convenciones son las que se automatizan.

Escribir software es una actividad de equipo. Como equipo, acordamos buenas prácticas y convenciones que nos permitan trabajar juntos y avanzar con paso rápido y seguro. Pero cualquier norma, por buena que sea, no sirve de nada si el equipo no es capaz de aplicarla de manera constante.

Aquí es donde ESLint brilla, porque permite alinear al equipo en torno a estas convenciones, que quedan documentadas en el archivo de configuración, y al mismo tiempo les libera de tener que recodarlas y aplicarlas cada vez.

Estas convenciones pueden incluir preferencias de sintaxis y nombrado, convenciones de estilo, prevención de errores lógicos o de sintaxis, detección de usos obsoletos de código, uso o evitación de ciertos patrones, entre otros.

Si, una y otra vez, nos encontramos corrigiendo o comentando un error recurrente con el equipo, es probable que exista una regla de ESLint que podemos añadir para automatizar la solución. Las mejores convenciones son las que se automatizan.


Anatomía de la configuración de ESLint

Antes de empezar con el caso práctico, revisamos las principales propiedades del objeto de configuración de ESLint:

Las reglas

Las reglas de ESLint (rules) están pensadas para ser completamente independientes las unas de las otras, activarse y desactivarse de forma individual. ESLint es una herramienta con la que imponer automáticamente nuestras visiones sobre el código, así que no hay regla que no podamos desactivar. Todo está sujeto a opinión, y dependerá de nuestras necesidades. Las reglas admiten tres posibles grados de severidad: "error", "warn" y "off", y pueden aceptar un array para configurar algunas opciones de forma más precisa. Muchas de ellas cuentan con capacidad de autofix, para corregir el error de forma automática.

Overrides

La propiedad overrides es muy importante en el sistema legacy, y también va a tener un papel destacado en la flat config. Es un array que acepta objetos en los que definimos configuraciones específicas para subconjuntos de archivos. Para definir cada subconjunto usamos las propiedades files y excludeFiles. Estas propiedades toman como valor expresiones globs relativas al directorio donde se localiza el archivo de configuración.

1module.exports = {
2  rules: {
3    // Reglas generales
4  },
5  overrides: [
6    {
7      files: ['**/*.{ts,tsx}'],
8      excludeFiles: '*.test.ts',
9      rules: {
10        // Reglas para archivos de Typescript
11      },
12    },
13    {
14      files: ['**/*stories.*'],
15      rules: {
16        // Reglas para archivos de Storybook
17      },
18    },
19  ],
20};

Overrides es una funcionalidad alternativa y más entendible frente al diseño en cascada, muy característico de ESLint, en el que profundizaremos en la parte 2 de esta serie.

Extends key vs plugins key

Hay algo que resulta bastante extraño en ESLint, y que más de una vez me ha causado confusión, es porque tenemos dependencias llamadas eslint-plugin-foo y otras llamadas eslint-config-foo. Y porque en unas ocasiones se indica que tenemos que usarlas con extends, y otras con plugins.

Como hemos dicho, ESLint es un sistema modular y configurable. Podemos instalar reglas adicionales para configurar nuestro caso de uso perfecto. Estas reglas vienen empaquetadas en dependencias NPM con el nombre de eslint-plugin-[my-plugin]. Para usarlas, las instalamos y pasamos el nombre al array de plugins: plugins: ["my-plugin"] (no es necesario usar el prefijo eslint-plugin-).

Pero esto no hace que nuestras reglas estén activas automáticamente. Cuando pasamos el valor al array de plugins, simplemente las estamos haciendo disponibles al sistema para su uso. Entonces podemos activar las que queramos en la propiedad rules:

1// .eslintrc.js
2
3module.exports = {
4  plugins: ['my-plugin'],
5  rules: {
6    // Las reglas van prefijadas con el nombre del plugin
7    'my-plugin/some-available-rule': 'error',
8  },
9};

Aquí es donde entran en juego las shareable configs (en adelante, configs). Para ahorrarnos el trabajo tedioso de tener que activar reglas una a una, existen otras dependencias de NPM con el nombre de eslint-config-[my-config], que activan directamente un conjunto de reglas pre-definidas al incluirlas en en array de extends: extends: ["my-config"] (no es necesario usar el prefijo eslint-config-)

Las configs pueden usar uno o varios plugins por debajo, pueden extenderse de otras configs y añadir por nosotros, además de reglas, cualquier otra configuración necesaria para su buen funcionamiento.

1module.exports = {
2  // the plugin is enabled under the hood and some recommended rules are applied
3  extends: ['my-config/recommended'],
4};

Finalmente, es habitual que los plugins que se comparten como dependencias traigan también consigo un set de configs que los autores han considerado de utilidad y que podemos usar en extends. Por ejemplo, eslint-plugin-react incluye como configs recommended, typescript, jsx-runtime, etc.

Esto puede resultar confuso, al exportar en una misma dependencia, de tipo plugin, tanto el propio plugin como un conjunto de configs. Pero resulta de lo más conveniente, porque nos permite tanto extender de una configuración pre-definida como aplicar reglas individuales.

Para usar una config importada de un plugin, seguimos la sintáxis: plugin:[name-plugin]/[name-config]

1module.exports = {
2  extends: ['plugin:my-plugin/recommended', 'plugin:my-plugin/strict'],
3  plugin: ['my-plugin'],
4  rules: {
5    'my-plugin/some-additional-rule': 'error',
6  },
7};

En resumen:

  • Las configs pueden contener todo lo que se pueda añadir a un archivo de configuración de ESLint, vienen paquetizadas como eslint-config-<my-config> y se pasan a la propiedad extends. Son la manera en la que podemos compartir y reusar configuraciones "listas para consumir", y ahorrarnos el trabajo de crearlas nosotros.

  • Los plugins añaden nuevas reglas al sistema. Vienen paquetizados como eslint-plugin-<my-plugin> y se pasan a la propiedad plugins, para poder activar reglas de forma individual en rules. También pueden exportar configs para activar conjuntos pre-definidos de esas reglas.

Otras root keys

Además de rules, overrides, extends y plugins, la configuración de ESLint incluye otras propiedades, como env, settings, parser, parserOptions, etc., que son esenciales para la funcionalidad de ESLint. Por ejemplo, a la de definir el comportamiento de plugins, de hacer que ESLint sea capaz de interpretar diferentes sintaxis, de que reconozca variables de entorno, etc. Veremos las más habituales a continuación, en nuestro caso práctico. Podemos fijarnos en su configuración, porque en la flat config (parte 2) se van a transformar y reorganizar.


Un caso práctico

¡Pasemos a la acción! En esta sección, vamos a crear incrementalmente una configuración de ESLint real, aunque simplificada para fines de ejemplo, que usamos para proyectos en producción con el siguiente stack:

  • React
  • Typescript
  • Storybook
  • Testing

Uno de los signos de identidad de ESLint, responsable en buena medida de su éxito, es su extensibilidad. El ecosistema de ESLint está formado por una gran variedad de plugins y configuraciones disponibles como paquetes NPM que podemos importar para establecer nuestro caso de uso.

En ocasiones puede abrumar la cantidad de dependencias que tenemos que instalar para configurar un proyecto1. A cambio, la ventaja es que podemos instalar exactamente lo que necesitemos:

Recomendaciones de ESLint

eslint:recommended contiene una serie de reglas que el equipo de ESLint, después de analizar muchísimos proyectos, considera de utilidad en la mayoría de casos. Así que lo primero que hacemos es incluir estas reglas en nuestra configuración. En el sistema tradicional, están incluidas dentro de ESLint, así que no es necesario instalar nada.

1//.eslintrc.js
2
3module.exports = {
4  extends: ['eslint:recommended'],
5};

Prettier

Prettier es un formateador, ESLint es un linter. Los formateadores son más rápidos y menos "inteligentes" que los linters, porque no entran a valorar la lógica del código. Se encargan reescribirlo siguiendo reglas de formateo puramente visual (tabs, espacios, puntos y comas, largos de línea...). Mientras que los linters entienden la lógica y la sintáxis, y nos dan indicaciones de acuerdo a cada una de las reglas activadas.

Cuando usamos Prettier y ESLint juntos, dado que Eslint contiene reglas de formateo, necesitamos instalar algo como eslint-config-prettier, para desactivar esas reglas e indicar a ESLint que Prettier va a ser el encargado del formateo.

El plugin eslint-plugin-prettier y variantes no están recomendadas en la gran mayoría de casos. Hacen que Prettier se comporte como una regla del linter, lo cual es mucho más lento. No hay necesidad de hacerlo así cuando tenemos configurado Prettier como herramienta independiente.

Cuando usamos Prettier y ESLint en el mismo proyecto, es importante que permitamos que cada herramienta realice la tarea que mejor sabe hacer.

1//.eslintrc.js
2
3module.exports = {
4  extends: [
5    'eslint:recommended',
6    // prettier debe figurar último, para resolver cualquier posible conflicto a su favor.
7    'prettier',
8  ],
9};

React

Instalaremos un par de plugins para incluir las reglas relacionadas con React y para que ESLint sea capaz de entender jsx, que no es una sintáxis nativa en Javascript: eslint-plugin-react y eslint-plugin-react-hooks. Éste último viene de manera separada porque hubo un tiempo en que los hooks no eran parte de React.

Los plugins necesitan conocer la version de React, porque de ello puede depender su funcionamiento, así que usamos settings para indicar a ESLint que mire en el package.json.

Usamos las configuraciones recomendadas en extends. Además añadimos a extends plugin:react/jsx-runtime, otra configuración que nos ayuda a desactivar las reglas que requieren que importemos React al inicio de cada archivo, lo cual no es necesario a partir de React 17.

Todas las configuraciones que hemos extendido, incluyen la opciones de parseo para que ESLint sea capaz de interpretar jsx. Aún así, lo añadimos de forma explícita al archivo para mayor claridad.

1//.eslintrc.js
2
3module.exports = {
4  extends: [
5    'eslint:recommended',
6    'plugin:react/recommended',
7    'plugin:react-hooks/recommended',
8    'plugin:react/jsx-runtime',
9    'prettier',
10  ],
11  settings: {
12    react: {
13      reactVersion: 'detect',
14    },
15  },
16  parserOptions: {
17    ecmaFeatures: {
18      jsx: true,
19    },
20  },
21  plugins: ['react', 'react-hooks'],
22  rules: {
23    // Aquí podemos activar manualmente reglas para los archivos de React
24  },
25};

Typescript

Para que ESLint sea capaz de entender los archivos de Typescript, necesitamos instalar @typescript-eslint, que contiene un parser y un montón de reglas recomendadas para trabajar con Typescript. En este caso, necesitamos hacer uso de overrides y crear un bloque donde pasamos globs para capturar los archivos con extensiones .ts y .tsx.

Vamos a extender la configuración recomendada, plugin:@typescript-eslint/recommended, pero también una segunda configuración, plugin:@typescript-eslint/recommended-requiring-type-checking, que hacen que ESLint sea mucho más potente al usar la información de tipos para detectar errores. Para que funcione, tenemos que facilitar a ESLint la información de tipos. Lo hacemos con project: true, que indica a ESLint que busque el tsconfig.json más cercano.

También vamos a extender plugin:@typescript-eslint/eslint-recommended. Lo que hace es desactivar las reglas de eslint:recommended que ya están controladas por Typescript, para evitar duplicidades.

1// .eslintrc.js
2
3module.exports = {
4  // Aqui van las propiedades generales que definimos con anterioridad
5  // ...
6  overrides: [
7    {
8      files: ['*.ts', '*.tsx'],
9      extends: [
10        'plugin:@typescript-eslint/eslint-recommended',
11        'plugin:@typescript-eslint/recommended',
12        'plugin:@typescript-eslint/recommended-requiring-type-checking',
13      ],
14      parser: '@typescript-eslint/parser',
15      parserOptions: {
16        project: true,
17      },
18      plugins: ['typescript-eslint'],
19      rules: {
20        // Aquí podemos activar manualmente reglas para los archivos de Typescript
21      },
22    },
23  ],
24};

Storybook

Instalamos el plugin oficial de Storybook para ESLint, que contiene reglas con las mejores prácticas para el tratamiento de las historias y del directorio de configuración .storybook: eslint-plugin-storybook. Por defecto, las reglas de este plugin solo aplican a los archivos que coincidad con los patrones: *.stories.* (recomendado) o *.story.*. Así que es muy importante que los nombres de los archivos de nuestras historias sigan esta convención, o el plugin no tendrá efecto.

En ESLint, los archivos que empiezan por punto no se analizan por defecto. Así que también necesitamos añadir el directorio .storybook a la lista de archivos que queremos analizar. Usamos un patrón con negación en ignorePatterns para que ESLint sea capaz de encontrar este directorio:

1//.eslintrc.js
2
3module.exports = {
4  extends: [
5    'eslint:recommended',
6    'plugin:react/recommended',
7    'plugin:react-hooks/recommended',
8    'plugin:react/jsx-runtime',
9    'plugin:storybook/recommended',
10    'prettier',
11  ],
12  ignorePatterns: [
13    '!.storybook'
14  ]
15  // El resto de propiedades generales
16  overrides: [
17    // Overrides para archivos Typescript
18  ],
19};

Accesibilidad

Usamos eslint-plugin-jsx-a11y para que nos ayude a detectar potenciales errores de accessibilidad en nuestros componentes de React. Simplemente extendemos las configuración recomendada.

1//.eslintrc.js
2
3module.exports = {
4  extends: [
5    'eslint:recommended',
6    'plugin:react/recommended',
7    'plugin:react-hooks/recommended',
8    'plugin:react/jsx-runtime',
9    'plugin:storybook/recommended',
10    'plugin:jsx-a11y/recommended',
11    'prettier',
12  ],
13  ignorePatterns: [
14    '!.storybook'
15  ]
16  // El resto de propiedades definidas
17  overrides: [
18    // Bloque para archivos Typescript
19  ],
20};

Imports

Con eslint-plugin-import podemos prevenir una serie de errores relaciones con la importación y exportación de módulos. Necesitaremos instalar eslint-import-resolver-typescript para tener soporte de Typescript.

Activamos algunas reglas específicas para este plugin:

  • A nivel estilístico, import/sort nos va a servir para ordernar y dividir en grupos los imports al principio de nuestros archivos de manera automática (es una regla con autofix), con lo cual va a ser mucho más facil entender las importaciones y evitar mantenerlas manualmente.
  • Activamos import/no-extraneous-dependencies para lanzar un error si importamos dependencias que no estén definidas en package.json.
  • Activamos import/no-default-export porque preferimos usar named exports. En algunos casos, como en las historias necesitamos permitir los default exports, así que vamos a habilitar un bloque en overrides para manejar este tipo de excepciones.
1// .eslintrc.js
2
3module.exports = {
4  extends: [
5    //...
6    'plugin:import/recommended',
7    'prettier',
8  ],
9  plugins: [
10    //...,
11    'import',
12  ],
13  settings: {
14    // Necesitamos estos settings para que ESLint sea capaz de resolver nuestros imports
15    'import/resolver': {
16      node: true,
17      typescript: true,
18    },
19  },
20  rules: {
21    'import/order': [
22      'error',
23      {
24        'newlines-between': 'always',
25        pathGroups: [
26          {
27            pattern: '$/**',
28            group: 'internal',
29          },
30        ],
31        pathGroupsExcludedImportTypes: ['builtin'],
32        groups: [
33          ['builtin', 'external'],
34          ['internal'],
35          ['parent', 'sibling', 'index'],
36          'unknown',
37        ],
38        alphabetize: {
39          order: 'asc',
40          caseInsensitive: true,
41        },
42      },
43    ],
44    'import/no-default-export': 'error',
45    'import/no-extraneous-dependencies': 'error',
46  },
47  overrides: [
48    {
49      files: ['*.ts', '*.tsx'],
50      extends: [
51        //...
52        'plugin:import/typescript',
53      ],
54    },
55    {
56      files: [
57        // Stories files
58        '*stories.*',
59        // Next pages files
60        'src/pages/**/*.tsx',
61        // Typescript declaration file
62        'additional.d.ts',
63        // Graphql Codegen generated file
64        '**/instrospection.ts',
65      ],
66      rules: {
67        'import/no-anonymous-default-export': 'off',
68        'import/no-default-export': 'off',
69      },
70    },
71  ],
72};

Testing

Para que nuestra configuración de ESLint sea capaz de interpretar los archivos de testing, también necesitamos hacer algunas adiciones, que dependerán de las herramientas que estemos usando.

  • Jest. Para usar Jest necesitamos activar en env la variable global jest, para que ESLint sea capaz de reconocerla. También necesitamos permitir que nuestros mocks contengan default exports.
1// .eslintrc.js
2
3module.exports = {
4  // Propiedades y reglas generales...
5  overrides: [
6    // Resto de bloques...
7    {
8      files: ['**/__tests__/**', '**/__mocks__/**'],
9      env: {
10        jest: true,
11      },
12    },
13    {
14      files: [
15        // Resto de paths...
16        '**/__mocks__/**',
17      ],
18      rules: {
19        'import/no-anonymous-default-export': 'off',
20        'import/no-default-export': 'off',
21      },
22    },
23  ],
24};
  • React Testing Library. Instalamos dos plugins: eslint-plugin-testing-library y eslint-plugin-jest-dom. Los usamos solo para nuestros archivos de testing:
1// .eslintrc.js
2
3module.exports = {
4  // Propiedades y reglas generales...
5  overrides: [
6    // Resto de bloques...
7    {
8      files: ['**/__tests__/**', '**/__mocks__/**'],
9      extends: ['plugin:testing-library/react', 'plugin:jest-dom/recommended'],
10      env: {
11        jest: true,
12      },
13      rules: {
14        'testing-library/no-render-in-setup': 'error',
15        'testing-library/no-wait-for-empty-callback': 'error',
16        'testing-library/prefer-explicit-assert': 'error',
17        'testing-library/prefer-presence-queries': 'error',
18        'testing-library/prefer-screen-queries': 'error',
19        'testing-library/prefer-wait-for': 'error',
20      },
21    },
22  ],
23};
  • Cypress. Para Cypress instalaremos otro plugin que solo analizará los archivos de la carpeta /cypress. En las parserOptions del bloque de Typescript vamos a modificar project para incluir la configuración de tipos de Cypress:
1// .eslintrc.js
2
3module.exports = {
4  // Propiedades y reglas generales...
5  overrides: [
6    // Resto de bloques...
7    {
8      files: ['*.ts', '*.tsx'],
9      //...
10      parserOptions: {
11        project: ['./tsconfig.json', './cypress/tsconfig.json'],
12      },
13    },
14    {
15      files: ['**/cypress/**'],
16      extends: ['plugin:cypress/recommended'],
17      env: {
18        'cypress/globals': true,
19      },
20    },
21  ],
22};

Gif of Office's character Michael Scott doing a ta-da! gesture
Et voilá 🎉! Aquí tenemos nuestro archivo final de configuración, integrando todas las partes que mencionamos y añadiendo algunos detalles, como la activación de un puñado de reglas individuales.

1//.eslintrc.js
2module.exports = {
3  root: true,
4  extends: [
5    'eslint:recommended',
6    'plugin:react/recommended',
7    'plugin:react-hooks/recommended',
8    'plugin:react/jsx-runtime',
9    'plugin:storybook/recommended',
10    'plugin:import/recommended',
11    'plugin:jsx-a11y/recommended',
12    'prettier',
13  ],
14  plugins: ['react', 'react-hooks', 'storybook', 'import', 'jsx-a11y'],
15  env: {
16    node: true,
17    browser: true,
18  },
19  settings: {
20    ecmaVersion: 'latest',
21    react: {
22      version: 'detect',
23    },
24    'import/resolver': {
25      node: true,
26      typescript: true,
27    },
28  },
29  parserOptions: {
30    ecmaFeatures: {
31      jsx: true,
32    },
33  },
34  ignorePatterns: [
35    '!*.js',
36    '!.storybook',
37    '.*.js',
38    '*.json',
39    'scripts',
40    'src/graphql/generated/*',
41  ],
42  rules: {
43    'no-console': 'warn',
44    'no-debugger': 'warn',
45    'no-warning-comments': 'warn',
46    'object-shorthand': 'error',
47    'no-param-reassign': [
48      'error',
49      {
50        props: true,
51        ignorePropertyModificationsFor: ['acc', 'next'],
52      },
53    ],
54    'react/prop-types': 'off',
55    'react/self-closing-comp': [
56      'error',
57      {
58        component: true,
59        html: true,
60      },
61    ],
62    'react/jsx-props-no-spreading': 'off',
63    'react/jsx-curly-brace-presence': [
64      'error',
65      { props: 'never', children: 'never' },
66    ],
67    'jsx-a11y/anchor-is-valid': [
68      'error',
69      {
70        components: ['Link', 'NextLink', 'RouterLink'],
71        aspects: ['invalidHref'],
72      },
73    ],
74    'import/order': [
75      'error',
76      {
77        'newlines-between': 'always',
78        pathGroups: [
79          {
80            pattern: '$/**',
81            group: 'internal',
82          },
83        ],
84        pathGroupsExcludedImportTypes: ['builtin'],
85        groups: [
86          ['builtin', 'external'],
87          ['internal'],
88          ['parent', 'sibling', 'index'],
89          'unknown',
90        ],
91        alphabetize: {
92          order: 'asc',
93          caseInsensitive: true,
94        },
95      },
96    ],
97    'import/no-default-export': 'error',
98    'import/no-extraneous-dependencies': 'error',
99  },
100  overrides: [
101    {
102      files: ['*.ts', '*.tsx'],
103      extends: [
104        'plugin:@typescript-eslint/eslint-recommended',
105        'plugin:@typescript-eslint/recommended',
106        'plugin:@typescript-eslint/recommended-requiring-type-checking',
107        'plugin:import/typescript',
108      ],
109      plugins: ['@typescript-eslint/eslint-plugin'],
110      parser: '@typescript-eslint/parser',
111      parserOptions: {
112        project: ['./tsconfig.json', './cypress/tsconfig.json'],
113      },
114      rules: {
115        '@typescript-eslint/no-use-before-define': [
116          'error',
117          { functions: false },
118        ],
119        '@typescript-eslint/no-floating-promises': [
120          'error',
121          { ignoreVoid: true },
122        ],
123      },
124    },
125    {
126      files: [
127        '*stories.*',
128        'src/pages/**/*.tsx',
129        'additional.d.ts',
130        '**/__mocks__/**',
131        'cypress.config.ts',
132      ],
133      rules: {
134        'import/no-anonymous-default-export': 'off',
135        'import/no-default-export': 'off',
136      },
137    },
138    {
139      files: ['**/__tests__/**', '**/__mocks__/**'],
140      env: {
141        jest: true,
142      },
143      extends: ['plugin:testing-library/react', 'plugin:jest-dom/recommended'],
144      rules: {
145        'testing-library/no-render-in-setup': 'error',
146        'testing-library/no-wait-for-empty-callback': 'error',
147        'testing-library/prefer-explicit-assert': 'error',
148        'testing-library/prefer-presence-queries': 'error',
149        'testing-library/prefer-screen-queries': 'error',
150        'testing-library/prefer-wait-for': 'error',
151      },
152    },
153    {
154      files: ['**/cypress/**'],
155      plugins: ['cypress'],
156      extends: ['plugin:cypress/recommended'],
157      env: {
158        'cypress/globals': true,
159      },
160    },
161  ],
162};

Tips para configurar el IDE y los scripts

Ahora que tenemos lista la configuración para el proyecto, podemos atender a otros aspectos que nos ayuden a la experiencia de uso de ESLint.

En primer lugar, podemos configurar Visual Studio Code en el contexto de nuestro proyecto, para que todos los miembros del equipo trabajen en un entorno con el mismo comportamiento. En la raíz del proyecto, creamos un directorio .vscode con dos archivos:

  • Uno llamado extensions.json, en el que podemos incluir las extensiones que recomendamos instalar, ESLint y Prettier (asumiendo que estamos usando ambas herramientas, como suele ocurrir). Estas extensiones integran las capacidades de estas herramientas en nuestro IDE, para informarnos de errores y warnings, y facilitar su resolución.
1//.vscode/extensions.json
2{
3  "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
4}
Screencapture of VSCode with a pop-up window which recommends to install ESLint extension

Visual Studio Code nos mostrará una ventana pop-up al abrir el proyecto con las extensiones recomendadas

  • Otro llamado settings.json, donde podemos configurar algunas funcionalidades que mejoren nuestra experiencia de desarrollo. Al guardar, ESLint corregirá todos los errores posibles y Prettier formateará nuestro código.
1//.vscode/settings.json
2{
3  "editor.defaultFormatter": "esbenp.prettier-vscode",
4  "editor.formatOnSave": true,
5
6  "editor.codeActionsOnSave": {
7    "source.fixAll.eslint": true
8  }
9}

Por último necesitamos configurar un par de scripts de ESLint en nuestro package.json, a fin de poder integrarlo en las distintas fases del proceso de desarrollo: al hacer commit, en el pipeline de CI, etc.

1{
2  "scripts": {
3    "lint": "eslint . --max-warnings 0 --report-unused-disable-directives --ignore-path .gitignore",
4    "lint-fix": "pnpm lint -- --fix"
5  }
6}

Éstas son algunas opciones útiles que podemos pasar al comando:

  • --max-warnings 0. En ESLint, una regla activada con un nivel de severidad warn, va a dar un aviso pero va a permitir que nuestro código compile. Esto puede hacer que el equipo interiorice que es posible y está bien ignorar las advertencias de ESLint. Como no queremos eso, no permitimos warnings.
  • --report-unused-disable-directives.En ocasiones, tenemos casos donde nos vemos obligados a desactivar alguna regla para un archivo o una línea concreta con un comentario tipo /* eslint-disable */. Esta opción nos avisa de los comentarios de desactivación de ESLint que hayan quedado obsoletos debido a refactors, etc.
  • --ignore-path .gitignore nos permite indicar a ESLint qué archivos no nos interesa analizar, por ejemplo node_modules.

Siguientes pasos 👋

En la parte 2 de esta guía, veremos como migrar nuestra configuración al nuevo sistema de ESLint, la flat config.


  1. Además de los plugins y configuraciones mencionados, otros que podrían ser interesante revisar para incluir en este tipo de aplicación: el plugin de NextJs, el plugin de SonarLint para escribir código limpio y eslint-plugin-filenames para ayudarnos a establecer convenciones de nombrado de archivos y directorios.

Tell me what you think. I would love to receive your feedback.

Would you like to invite me to give a talk about this at your event? Is there some topic of your interest you want me to write about?

Drop me a message