Skip to content

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.

// ESLint avisa: 'total' está declarada pero nunca se usa
const total = 0
// ESLint avisa: falta el return en algunas ramas
items.map((item) => {
if (item.activo) return item.nombre
})
<!-- ESLint avisa: v-for sin :key -->
<li v-for="curso in cursos">{{ curso.titulo }}</li>
  1. 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 un pre-commit, aborta el commit.
  2. Plugins: paquetes que añaden reglas nuevas para un ecosistema concreto. eslint-plugin-vue añade las reglas vue/*; @typescript-eslint/eslint-plugin añade las @typescript-eslint/*.

  3. 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) necesita vue-eslint-parser.

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.

eslint.config.js (estructura)
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
]

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”
'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í:

  1. 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 interface o enum).
  2. 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 usa
items.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 catch
try {
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 error
'no-redeclare': RULES.OFF, // Permitir overloads de TypeScript

En 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.

ReglaEfectoEjemplo
vue/component-name-in-template-casing: PascalCaseLos componentes se escriben en PascalCase en el template<AECardSlider />, no <ae-card-slider />
vue/html-quotes: doubleAtributos con comillas doblesclass="card"
vue/mustache-interpolation-spacing: alwaysEspacios dentro de las interpolaciones{{ titulo }}, no {{titulo}}
vue/v-on-style: shorthandAtajo para eventos@click, no v-on:click
vue/v-bind-style: shorthandAtajo para props dinámicas:src, no v-bind:src
vue/require-v-for-key: errorTodo v-for necesita :keyevita bugs de renderizado en listas
vue/no-mutating-props: errorUn hijo no modifica las props que recibefuerza emit para comunicar hacia arriba
vue/no-multi-spaces: errorSin espacios dobles en el template
vue/no-spaces-around-equal-signs-in-attributeclass="x", no class = "x"

Y las que relajamos a propósito:

Regla desactivadaPor qué
vue/multi-word-component-namesPermite componentes de una sola palabra (Footer.vue, Hero.vue).
vue/require-default-propNo obliga a dar valor por defecto a cada prop opcional.
vue/first-attribute-linebreakChoca con el formato de Prettier; gana Prettier.
vue/singleline-html-element-content-newlinePermite <span>Texto</span> en una sola línea.
vue/no-setup-props-destructureVue 3.5+ ya soporta destructuring reactivo de props.
vue/no-v-text-v-html-on-componentNecesitamos v-html en componentes para pintar contenido de Strapi (saneado con DOMPurify).
'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: [
'**/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 usa project: './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) y server/** a tener en cuenta — el código de Nitro queda hoy fuera del linter.
Terminal window
pnpm lint # eslint . → solo reporta
pnpm lint:fix # eslint . --fix → reporta y arregla lo que puede

Recuerda que --fix también aplica Prettier, por la integración descrita arriba.

Con dbaeumer.vscode-eslint instalada, los errores se subrayan mientras escribes. Para que además se arreglen al guardar:

.vscode/settings.json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}