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.
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
# 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 initESLint 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.
// @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
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf"
}node_modules
dist
.next
coverage
*.json
package-lock.jsonHusky + 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.
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# Roda lint-staged: lint apenas nos arquivos staged (não no projeto todo)
npx lint-staged{
"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
{
"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.