ESLint
ESLint es el linter de JavaScript. A diferencia de Prettier, sí opina sobre tu código: detecta patrones que compilan pero que son casi siempre un error o una mala práctica.
Qué detecta
Section titled “Qué detecta”// ESLint avisa: 'total' está declarada pero nunca se usaconst total = 0
// ESLint avisa: falta el return en algunas ramasitems.map((item) => { if (item.activo) return item.nombre})<!-- ESLint avisa: v-for sin :key --><li v-for="curso in cursos">{{ curso.titulo }}</li>Los tres conceptos que hay que entender
Section titled “Los tres conceptos que hay que entender”-
Reglas: cada comprobación individual, con un nombre (
no-unused-vars,vue/require-v-for-key). Cada regla se configura en uno de tres niveles:off— desactivada.warn— avisa, pero no rompe nada.error— falla. En unpre-commit, aborta el commit.
-
Plugins: paquetes que añaden reglas nuevas para un ecosistema concreto.
eslint-plugin-vueañade las reglasvue/*;@typescript-eslint/eslint-pluginañade las@typescript-eslint/*. -
Parsers: ESLint por sí solo solo entiende JavaScript. Para leer TypeScript necesita
@typescript-eslint/parser, y para leer un.vue(que es HTML + JS + CSS en un mismo archivo) necesitavue-eslint-parser.
eslint.config.js: el “flat config”
Section titled “eslint.config.js: el “flat config””Desde ESLint 9 la configuración es un array de objetos, y cada objeto se aplica a los archivos que indique su clave files. Se leen en orden, y lo que viene después sobrescribe lo anterior. Esa es toda la lógica que hay que retener para leer nuestro archivo.
export default [ js.configs.recommended, // 1. base para todo { ignores: [] }, // 2. qué no mirar nunca { files: ['**/*.js', '**/*.ts'] }, // 3. JS y TS { files: ['**/*.ts'] }, // 4. solo TS (parser con tipos) { files: ['**/*.vue'] }, // 5. solo Vue { files: ['eslint.config.js'] }, // 6. el propio config prettierConfig, // 7. apaga reglas que chocan con Prettier]La constante RULES
Section titled “La constante RULES”Nuestros configs empiezan con este atajo, para no escribir cadenas sueltas por todo el archivo:
const RULES = { OFF: 'off', WARN: 'warn', ERROR: 'error',}No es una función de ESLint, es simplemente un objeto nuestro. RULES.ERROR es literalmente la cadena error.
Reglas destacadas de nuestra configuración
Section titled “Reglas destacadas de nuestra configuración”Variables sin usar, con vía de escape
Section titled “Variables sin usar, con vía de escape”'no-unused-vars': RULES.OFF, // se apaga la regla base de JS...'@typescript-eslint/no-unused-vars': [ // ...y se usa la versión de TypeScript RULES.ERROR, { args: 'all', argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', ignoreRestSiblings: true, },],Dos ideas aquí:
- Se apaga la regla base y se activa la de TypeScript. Es obligatorio hacerlo así: si dejas las dos activas, la de JS no entiende los tipos y reporta falsos positivos (por ejemplo en
interfaceoenum). - El prefijo
_es la vía de escape. Si necesitas declarar algo que no vas a usar, lo llamas con_delante y ESLint lo ignora:
// ❌ error: 'index' no se usaitems.forEach((item, index) => console.log(item))
// ✅ correcto: el guion bajo indica "sé que no lo uso"items.forEach((item, _index) => console.log(item))
// ✅ también sirve en los catchtry { cargarDatos()} catch (_error) { mostrarMensajeGenerico()}ignoreRestSiblings: true permite además el patrón de “quitar una propiedad de un objeto”:
const { password, ...usuarioPublico } = usuario // password no se marca como errorno-redeclare desactivada
Section titled “no-redeclare desactivada”'no-redeclare': RULES.OFF, // Permitir overloads de TypeScriptEn TypeScript es legítimo declarar la misma función varias veces con firmas distintas (overloads). La regla base de JavaScript lo interpretaría como un error.
Reglas de Vue que definen nuestro estilo
Section titled “Reglas de Vue que definen nuestro estilo”| Regla | Efecto | Ejemplo |
|---|---|---|
vue/component-name-in-template-casing: PascalCase | Los componentes se escriben en PascalCase en el template | <AECardSlider />, no <ae-card-slider /> |
vue/html-quotes: double | Atributos con comillas dobles | class="card" |
vue/mustache-interpolation-spacing: always | Espacios dentro de las interpolaciones | {{ titulo }}, no {{titulo}} |
vue/v-on-style: shorthand | Atajo para eventos | @click, no v-on:click |
vue/v-bind-style: shorthand | Atajo para props dinámicas | :src, no v-bind:src |
vue/require-v-for-key: error | Todo v-for necesita :key | evita bugs de renderizado en listas |
vue/no-mutating-props: error | Un hijo no modifica las props que recibe | fuerza emit para comunicar hacia arriba |
vue/no-multi-spaces: error | Sin espacios dobles en el template | |
vue/no-spaces-around-equal-signs-in-attribute | class="x", no class = "x" |
Y las que relajamos a propósito:
| Regla desactivada | Por qué |
|---|---|
vue/multi-word-component-names | Permite componentes de una sola palabra (Footer.vue, Hero.vue). |
vue/require-default-prop | No obliga a dar valor por defecto a cada prop opcional. |
vue/first-attribute-linebreak | Choca con el formato de Prettier; gana Prettier. |
vue/singleline-html-element-content-newline | Permite <span>Texto</span> en una sola línea. |
vue/no-setup-props-destructure | Vue 3.5+ ya soporta destructuring reactivo de props. |
vue/no-v-text-v-html-on-component | Necesitamos v-html en componentes para pintar contenido de Strapi (saneado con DOMPurify). |
Prettier como regla de ESLint
Section titled “Prettier como regla de ESLint”'prettier/prettier': ['error', { endOfLine: 'auto' }],Esto hace que un archivo mal formateado sea un error de ESLint, y por tanto que eslint --fix lo formatee.
endOfLine: 'auto' es un detalle importante para nuestro equipo: respeta el final de línea que ya tiene el archivo en lugar de forzar LF. Sin esta opción, trabajando en Windows (donde Git suele dejar los archivos en CRLF) todos los archivos aparecerían como error de formato.
ignores: qué no se lintea
Section titled “ignores: qué no se lintea”ignores: [ '**/node_modules/**', '**/.nuxt/**', '**/dist/**', '**/.output/**', '**/.nitro/**', '**/coverage/**', '**/static/**', '**/.husky/**', 'commitlint.config.ts', 'nuxt.config.ts', 'vitest.config.ts', '*.log', '.env*', '.cache/**', '.yarn/**', '.DS_Store', 'Thumbs.db', '**/server/**',]Tres grupos:
- Código generado o instalado:
node_modules,.nuxt,dist,.output,.nitro,coverage,static. No lo escribimos nosotros. - Archivos de configuración:
commitlint.config.ts,nuxt.config.ts,vitest.config.ts. Se excluyen porque el bloque de TypeScript usaproject: './tsconfig.json'y estos archivos no siempre entran en ese proyecto, lo que provocaría un error del parser. - Otros:
.env*(nunca se lintean secretos) yserver/**a tener en cuenta — el código de Nitro queda hoy fuera del linter.
Comandos
Section titled “Comandos”pnpm lint # eslint . → solo reportapnpm lint:fix # eslint . --fix → reporta y arregla lo que puedeRecuerda que --fix también aplica Prettier, por la integración descrita arriba.
Extensión de VS Code
Section titled “Extensión de VS Code”Con dbaeumer.vscode-eslint instalada, los errores se subrayan mientras escribes. Para que además se arreglen al guardar:
{ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }}