Voltar para o Blog

ESLint 9 e Flat Config: Guia Completo de Migração e Configuração

Olá HaWkers, o ESLint 9 trouxe uma mudança significativa na forma como configuramos o linter: o Flat Config. Se você ainda está usando .eslintrc.js ou .eslintrc.json, é hora de entender o novo sistema e migrar seus projetos.

Neste guia vamos cobrir desde os conceitos básicos até configurações avançadas para projetos TypeScript e React.

Por Que o Flat Config?

O sistema antigo de configuração do ESLint tinha algumas limitações:

Problemas do sistema antigo:

  • Cascata confusa de configurações
  • Resolução complexa de plugins
  • Dificuldade em entender qual regra vem de onde
  • Performance afetada pela busca de arquivos de configuração

Benefícios do Flat Config:

  • Arquivo único de configuração (eslint.config.js)
  • Imports explícitos de plugins e configurações
  • Melhor performance
  • Mais fácil de debugar e entender
  • Suporte nativo a ESM

Estrutura Básica do Flat Config

O Flat Config usa um arquivo eslint.config.js (ou .mjs, .cjs) na raiz do projeto:

// eslint.config.js
import js from '@eslint/js';

export default [
  js.configs.recommended,
  {
    rules: {
      'no-unused-vars': 'warn',
      'no-console': 'off',
    },
  },
];

Anatomia de uma Configuração

// eslint.config.js
export default [
  // Configuração 1: Aplica a todos os arquivos
  {
    rules: {
      'semi': ['error', 'always'],
    },
  },

  // Configuração 2: Aplica apenas a arquivos específicos
  {
    files: ['**/*.ts', '**/*.tsx'],
    rules: {
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },

  // Configuração 3: Ignora arquivos
  {
    ignores: ['dist/**', 'node_modules/**', '*.config.js'],
  },
];

Migração do Sistema Antigo

Antes: .eslintrc.js

// .eslintrc.js (antigo)
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 'latest',
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint', 'react', 'react-hooks'],
  rules: {
    'react/react-in-jsx-scope': 'off',
    '@typescript-eslint/no-unused-vars': 'warn',
  },
};

Depois: eslint.config.js

// eslint.config.js (novo)
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import prettier from 'eslint-config-prettier';
import globals from 'globals';

export default [
  js.configs.recommended,

  // Configuração global
  {
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      globals: {
        ...globals.browser,
        ...globals.node,
        ...globals.es2021,
      },
    },
  },

  // TypeScript
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: typescriptParser,
      parserOptions: {
        ecmaFeatures: { jsx: true },
      },
    },
    plugins: {
      '@typescript-eslint': typescript,
    },
    rules: {
      ...typescript.configs.recommended.rules,
      '@typescript-eslint/no-unused-vars': 'warn',
    },
  },

  // React
  {
    files: ['**/*.jsx', '**/*.tsx'],
    plugins: {
      react,
      'react-hooks': reactHooks,
    },
    rules: {
      ...react.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      'react/react-in-jsx-scope': 'off',
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
  },

  // Prettier (deve ser o último)
  prettier,

  // Ignores
  {
    ignores: ['dist/**', 'node_modules/**', '.next/**'],
  },
];

Configurações Comuns

Projeto React + TypeScript

// eslint.config.js
import js from '@eslint/js';
import typescript from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import globals from 'globals';

export default [
  js.configs.recommended,
  ...typescript.configs.recommended,

  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.es2021,
      },
    },
    plugins: {
      react,
      'react-hooks': reactHooks,
      'jsx-a11y': jsxA11y,
    },
    rules: {
      // React
      'react/prop-types': 'off',
      'react/react-in-jsx-scope': 'off',
      'react/jsx-uses-react': 'off',

      // Hooks
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',

      // Acessibilidade
      'jsx-a11y/alt-text': 'error',
      'jsx-a11y/anchor-is-valid': 'warn',

      // TypeScript
      '@typescript-eslint/no-unused-vars': ['warn', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
    settings: {
      react: { version: 'detect' },
    },
  },

  {
    ignores: ['dist/**', 'build/**', 'node_modules/**'],
  },
];

Projeto Node.js + TypeScript

// eslint.config.js
import js from '@eslint/js';
import typescript from 'typescript-eslint';
import node from 'eslint-plugin-n';
import globals from 'globals';

export default [
  js.configs.recommended,
  ...typescript.configs.recommended,

  {
    files: ['**/*.{js,ts}'],
    languageOptions: {
      globals: {
        ...globals.node,
        ...globals.es2021,
      },
    },
    plugins: {
      n: node,
    },
    rules: {
      // Node.js
      'n/no-missing-import': 'off', // TypeScript cuida disso
      'n/no-unsupported-features/es-syntax': 'off',
      'n/no-process-exit': 'warn',

      // TypeScript
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': ['warn', {
        allowExpressions: true,
      }],

      // Geral
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'prefer-const': 'error',
    },
  },

  {
    ignores: ['dist/**', 'node_modules/**', '*.js'],
  },
];

Plugins e Suas Novas Configurações

@typescript-eslint

O typescript-eslint v8+ tem suporte nativo ao Flat Config:

import typescript from 'typescript-eslint';

export default [
  // Usa o helper do typescript-eslint
  ...typescript.configs.recommended,

  // Ou configuração mais estrita
  ...typescript.configs.strict,

  // Customizações
  {
    files: ['**/*.ts', '**/*.tsx'],
    rules: {
      '@typescript-eslint/consistent-type-imports': 'error',
      '@typescript-eslint/no-floating-promises': 'error',
    },
  },
];

eslint-plugin-import

import importPlugin from 'eslint-plugin-import';

export default [
  {
    plugins: {
      import: importPlugin,
    },
    rules: {
      'import/order': ['error', {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index',
        ],
        'newlines-between': 'always',
        alphabetize: { order: 'asc' },
      }],
      'import/no-duplicates': 'error',
      'import/no-unresolved': 'error',
    },
    settings: {
      'import/resolver': {
        typescript: true,
        node: true,
      },
    },
  },
];

Dicas de Migração

1. Use o Migration Tool

O ESLint oferece uma ferramenta de migração:

npx @eslint/migrate-config .eslintrc.js

2. Verifique Compatibilidade de Plugins

Nem todos os plugins foram atualizados para Flat Config. Verifique a documentação de cada um:

// Alguns plugins ainda usam o formato antigo
// Use o compatibility layer se necessário
import { FlatCompat } from '@eslint/eslintrc';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

export default [
  // Plugins modernos
  js.configs.recommended,

  // Plugins legados via compat
  ...compat.extends('plugin:legacy-plugin/recommended'),

  // Suas regras
  {
    rules: {
      // ...
    },
  },
];

3. Remova Arquivos Antigos

Após migrar, remova os arquivos de configuração antigos:

rm .eslintrc.js .eslintrc.json .eslintrc.yaml .eslintignore

O Flat Config usa a propriedade ignores no próprio arquivo de configuração.

4. Atualize os Scripts

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

Debugging da Configuração

Ver Configuração Final

npx eslint --print-config src/index.ts

Verificar Quais Arquivos São Lintados

npx eslint . --debug 2>&1 | grep "Linting"

Testar Regras Específicas

// eslint.config.js - temporariamente
export default [
  {
    files: ['**/*.ts'],
    rules: {
      // Teste apenas uma regra
      'no-console': 'error',
    },
  },
];

Conclusão

O Flat Config do ESLint 9 representa uma evolução significativa na forma como configuramos linting em projetos JavaScript e TypeScript. Embora a migração exija algum esforço inicial, os benefícios de clareza, performance e manutenibilidade compensam.

Recomendações finais:

  • Migre projetos novos diretamente para Flat Config
  • Para projetos existentes, use a ferramenta de migração como ponto de partida
  • Verifique a compatibilidade dos plugins antes de migrar
  • Aproveite para revisar e simplificar suas regras durante a migração

Se você está interessado em mais ferramentas para melhorar a qualidade do seu código, recomendo conferir o artigo Debugging JavaScript: Técnicas Avançadas com DevTools que complementa bem as práticas de linting com técnicas de debugging.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário