Voltar para Artigos
DevOps6 min de leitura

Padronização Impecável: ESLint 9 com Flat Config, Prettier e Husky

ESLint 9 com Flat Config (eslint.config.js), regras para TypeScript estrito, integração com Prettier sem conflitos, lint-staged para lint apenas em arquivos alterados e enforcement de tipos com type-aware linting.

12 de agosto de 2026

Code reviews consumidos por debates sobre aspas simples vs duplas, ponto e vírgula obrigatório, e largura de linha máxima são desperdício de tempo e fonte de conflito desnecessário. Essas decisões devem ser automatizadas e enforçadas por ferramentas — não por consenso humano a cada PR.

A trinca ESLint + Prettier + Husky + lint-staged resolve isso: o Prettier decide a formatação visual, o ESLint detecta padrões problemáticos (any implícito, variáveis não usadas, promessas não tratadas), o Husky garante que nenhum commit passe sem passar pelo lint, e o lint-staged garante que apenas os arquivos alterados sejam verificados.

Instalação

bash
# ESLint 9+ com TypeScript
npm install -D eslint @eslint/js typescript-eslint

# Prettier + integração com ESLint
npm install -D prettier eslint-config-prettier

# Husky + lint-staged
npm install -D husky lint-staged
npx husky init

ESLint 9: Flat Config

O ESLint 9 migrou para o formato Flat Config (eslint.config.js) — sem mais .eslintrc.json. É mais explícito, mais fácil de compor e suporta ESM nativo. A config abaixo usa strictTypeChecked — o preset mais rigoroso, que ativa o type-aware linting usando o TypeScript compiler. É mais lento que o lint normal, mas detecta erros que só aparecem em runtime: promessas não tratadas, await em valores não-thenable, comparações sem sentido por incompatibilidade de tipos.

eslint.config.js
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';

export default tseslint.config(
  // Configurações base do ESLint
  eslint.configs.recommended,

  // TypeScript com type-aware linting
  // Mais lento (usa tsc), mas detecta erros de tipo nas regras
  ...tseslint.configs.strictTypeChecked,
  ...tseslint.configs.stylisticTypeChecked,

  {
    // Configurações de parser TypeScript
    languageOptions: {
      parserOptions: {
        project: true,             // Usa o tsconfig.json do projeto
        tsconfigRootDir: import.meta.dirname,
      },
    },

    rules: {
      // TypeScript strict
      '@typescript-eslint/no-explicit-any': 'error',
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      '@typescript-eslint/explicit-function-return-type': 'off', // Inferência é ok
      '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],

      // Promises: detecta async sem await e promessas não tratadas
      '@typescript-eslint/no-floating-promises': 'error',
      '@typescript-eslint/await-thenable': 'error',

      // Padrões gerais
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'prefer-const': 'error',
      'no-var': 'error',
    },
  },

  {
    // Arquivos de teste: regras mais permissivas
    files: ['**/*.spec.ts', '**/*.test.ts'],
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-non-null-assertion': 'off',
    },
  },

  {
    // Ignora arquivos gerados e dependências
    ignores: ['node_modules/**', 'dist/**', 'coverage/**', '*.js'],
  },

  // DEVE ser o último: desativa regras de formatação que conflitam com Prettier
  eslintConfigPrettier
);

Prettier: Formatação Sem Debate

.prettierrc
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf"
}
.prettierignore
node_modules
dist
.next
coverage
*.json
package-lock.json

Husky + lint-staged: Enforcement no Git

O husky init cria o hook pre-commit automaticamente. O lint-staged é a peça essencial: sem ele, o hook rodaria o lint em todos os arquivos do projeto a cada commit — o que em um projeto grande pode demorar minutos. O lint-staged intercepta apenas os arquivos no stage do Git (git diff --cached), tornando o hook rápido o suficiente para não frustrar o desenvolvedor.

pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# Roda lint-staged: lint apenas nos arquivos staged (não no projeto todo)
npx lint-staged
package.json
{
  "scripts": {
    "prepare": "husky",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "prettier --write",
      "eslint --fix",
      "eslint"  // Roda sem --fix por último — falha se ainda houver erros
    ],
    "*.{json,md,yml,yaml}": [
      "prettier --write"
    ]
  }
}

O type-aware linting (strictTypeChecked) detecta problemas que o linting normal não consegue: chamar await em um valor que não é uma Promise, usar .then() em um valor que não é thenable, e comparações sempre-verdadeiras por incompatibilidade de tipos. É mais lento (usa o TypeScript compiler), mas vale o custo para projetos de médio a grande porte.

VSCode: Integração para DX Excelente

settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "typescript.tsdk": "node_modules/typescript/lib",
  "eslint.useFlatConfig": true
}

Conclusão

Configurar essa stack no primeiro dia de projeto é um investimento de 30 minutos que economiza horas de debate em code reviews ao longo de meses. O Prettier elimina discussões de estilo. O ESLint com type-aware linting captura bugs antes do runtime. O Husky garante que o repositório nunca receba código que viola as regras — sem depender da disciplina individual de cada desenvolvedor.