Imagine entrar em um projeto onde cada arquivo tem um estilo diferente — aspas simples em um, duplas em outro, ponto e vírgula em alguns, ausente em outros, indentação com 2 espaços aqui e 4 ali. Agora imagine o mesmo projeto sendo mantido por cinco desenvolvedores sem nenhuma padronização.
É caos. E esse caos tem custo real: revisões de código que discutem estilo em vez de lógica, diffs enormes por mudanças de formatação, e bugs introduzidos por erros que uma ferramenta detectaria em segundos.
ESLint e Prettier resolvem isso — um encontra problemas no código, o outro garante que todos escrevam da mesma forma.
ESLint vs Prettier — papéis diferentes
┌─────────────────────────────────────────────────────────┐
│ ESLINT — Linter │
│ │
│ "Seu código tem problemas" │
│ ✗ Variável declarada mas nunca usada │
│ ✗ == em vez de === │
│ ✗ console.log esquecido │
│ ✗ Função sem return explícito │
│ ✗ Import não utilizado │
│ Foco: qualidade e correção │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ PRETTIER — Formatter │
│ │
│ "Seu código vai ficar assim" │
│ → Aspas simples ou duplas (você escolhe) │
│ → Ponto e vírgula ou não │
│ → 80 ou 120 colunas por linha │
│ → 2 ou 4 espaços de indentação │
│ → Vírgula no último item de objeto │
│ Foco: consistência visual — sem debates │
└─────────────────────────────────────────────────────────┘
Usados juntos: o Prettier formata, o ESLint encontra problemas lógicos.
Instalando ESLint
npm install -D eslint
# Inicializar configuração interativamente
npx eslint --init
Ou criar manualmente:
npm install -D eslint @eslint/js
// eslint.config.js (formato moderno — ESLint 9+)
const js = require("@eslint/js");
module.exports = [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
sourceType: "commonjs",
globals: {
require: "readonly",
module: "readonly",
exports: "readonly",
__dirname: "readonly",
__filename: "readonly",
process: "readonly",
console: "readonly",
},
},
rules: {
// ── Erros graves ─────────────────────────────
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"no-undef": "error",
"no-unreachable": "error",
"no-duplicate-case": "error",
// ── Boas práticas ────────────────────────────
"eqeqeq": ["error", "always"], // sempre === nunca ==
"no-console": "warn", // avisa sobre console.log
"no-var": "error", // proíbe var
"prefer-const": "error", // prefere const
"prefer-arrow-callback": "warn", // arrow functions em callbacks
// "no-return-await" foi DESCONTINUADA no ESLint 8.46 — e com razão:
// dentro de um try, "return await" não é redundante, é o que mantém a
// função presente para capturar a rejeição.
"require-await": "error", // async sem await
"no-throw-literal": "error", // throw new Error(), não throw "string"
// ── Estilo (o Prettier cuida do resto) ───────
"no-multiple-empty-lines": ["warn", { max: 2 }],
"no-trailing-spaces": "warn",
},
},
// ignores GLOBAL precisa de um objeto só para ele. Dentro do objeto de
// regras, como estava antes, ele só limita o alcance daquele bloco — e
// o node_modules continuaria sendo analisado.
{ ignores: ["node_modules/", "coverage/", "dist/", "build/"] },
];
Rodando o ESLint
# Verificar todos os arquivos da pasta src
npx eslint src/
# Verificar um arquivo específico
npx eslint src/index.js
# Corrigir automaticamente o que for possível
npx eslint src/ --fix
# Saída típica:
# src/controllers/authController.js
# 12:5 error 'token' is assigned a value but never used no-unused-vars
# 34:9 warning Unexpected console statement no-console
# 67:3 error Expected '===' and instead saw '==' eqeqeq
#
# ✖ 3 problems (2 errors, 1 warning)
# 0 errors and 1 warning potentially fixable with the `--fix` option.
Regras do ESLint — entendendo os níveis
{
rules: {
// "off" ou 0 → regra desabilitada
// "warn" ou 1 → aviso (não falha o build)
// "error" ou 2 → erro (falha o build)
"no-console": "off", // desabilitada
"no-console": "warn", // apenas aviso
"eqeqeq": "error", // falha o build
// Regras com opções
"no-unused-vars": ["error", {
vars: "all", // todas as variáveis
args: "after-used", // args usados após o último usado
argsIgnorePattern: "^_", // ignora args começando com _
ignoreRestSiblings: true,
}],
"prefer-const": ["error", {
destructuring: "any", // const se qualquer var pode ser const
}],
}
}
Ignorando arquivos e linhas
# .eslintignore — NAO funciona mais a partir do ESLint 9: o arquivo foi
# removido junto com o formato antigo de configuração. Hoje o único caminho
# é o objeto { ignores: [...] } no eslint.config.js. O conteúdo abaixo fica
# como referência para quem ainda mantém projeto no ESLint 8.
node_modules/
dist/
coverage/
*.min.js
public/
// Ignorar linha específica
const x = 1; // eslint-disable-line no-unused-vars
// Ignorar próxima linha
// eslint-disable-next-line no-console
console.log("debug");
// Ignorar bloco inteiro
/* eslint-disable no-console */
console.log("início do bloco");
console.log("meio do bloco");
/* eslint-enable no-console */
// ⚠️ Use com moderação — desabilitar regras
// esconde problemas reais
Instalando e configurando o Prettier
npm install -D prettier
// .prettierrc
{
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "es5",
"tabWidth": 2,
"useTabs": false,
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
# .prettierignore
node_modules/
dist/
coverage/
package-lock.json
*.min.js
O que o Prettier faz na prática
// ── Antes do Prettier ────────────────────────────
const usuario={nome:'Ana', email:"ana@email.com", idade:28}
function saudar(nome, saudacao='Olá'){
return `${saudacao}, ${nome}!`
}
const numeros = [1,2,3,4,5]
const dobro = numeros.map(n=>n*2)
// ── Depois do Prettier ───────────────────────────
const usuario = { nome: 'Ana', email: 'ana@email.com', idade: 28 };
function saudar(nome, saudacao = 'Olá') {
return `${saudacao}, ${nome}!`;
}
const numeros = [1, 2, 3, 4, 5];
const dobro = numeros.map((n) => n * 2);
O Prettier não pergunta — ele decide. Essa é a vantagem: fim dos debates de estilo.
Rodando o Prettier
# Verificar arquivos (não modifica)
npx prettier --check src/
# Formatar todos os arquivos
npx prettier --write src/
# Formatar arquivo específico
npx prettier --write src/index.js
# Saída do --check:
# Checking formatting...
# [warn] src/controllers/authController.js
# [warn] src/utils/validacoes.js
# [warn] Code style issues found in 2 files.
# Run Prettier to fix.
Integrando ESLint + Prettier — sem conflitos
O ESLint tem regras de formatação que conflitam com o Prettier. A solução é desabilitar essas regras do ESLint e deixar apenas o Prettier cuidar da formatação:
npm install -D eslint-config-prettier
// eslint.config.js — adicione prettier ao final
const js = require("@eslint/js");
const prettier = require("eslint-config-prettier");
module.exports = [
js.configs.recommended,
{
// ... suas regras
},
prettier, // deve ser o ÚLTIMO — desabilita regras conflitantes
];
Agora ESLint cuida da qualidade, Prettier cuida do estilo — sem sobreposição.
Scripts no package.json
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"quality": "npm run format:check && npm run lint"
}
}
npm run lint # verifica problemas
npm run lint:fix # corrige o que é possível automaticamente
npm run format # formata todos os arquivos
npm run quality # executa ambas as verificações
Configuração do VS Code
Para que o editor formate automaticamente ao salvar:
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Extensões necessárias:
esbenp.prettier-vscode— Prettierdbaeumer.vscode-eslint— ESLint
Husky + lint-staged — validação no commit
Garante que ninguém commite código com erros — o hook roda automaticamente antes de cada git commit:
npm install -D husky lint-staged
# Inicializa o Husky
npx husky init
# .husky/pre-commit
npx lint-staged
// package.json
{
"lint-staged": {
"src/**/*.js": [
"prettier --write",
"eslint --fix",
"eslint"
],
"*.{json,md}": [
"prettier --write"
]
}
}
Agora, ao tentar fazer commit:
git add .
git commit -m "feat: adiciona rota de usuários"
# ✔ Prettier formatou 3 arquivos
# ✔ ESLint corrigiu 1 problema
# ✖ ESLint encontrou 1 erro não corrigível:
# src/controllers/authController.js:42
# 'usuario' is assigned a value but never used
#
# ✖ Commit bloqueado. Corrija os erros antes de commitar.
O commit só passa se o código estiver limpo.
EditorConfig — padronização entre editores
Mesmo antes do Prettier, o EditorConfig garante que diferentes editores (VS Code, WebStorm, Vim) usem a mesma indentação:
# .editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.{json,yml,yaml}]
indent_size = 2
Configuração completa para projeto Node.js
Aqui está uma configuração profissional e pronta para usar:
# Instalar tudo de uma vez
npm install -D \
eslint \
@eslint/js \
prettier \
eslint-config-prettier \
husky \
lint-staged
// eslint.config.js
const js = require("@eslint/js");
const prettier = require("eslint-config-prettier");
module.exports = [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
sourceType: "commonjs",
globals: {
require: "readonly",
module: "readonly",
exports: "readonly",
__dirname: "readonly",
__filename: "readonly",
process: "readonly",
console: "readonly",
Buffer: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
setInterval: "readonly",
clearInterval: "readonly",
},
},
rules: {
"no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"no-undef": "error",
"eqeqeq": ["error", "always"],
"no-var": "error",
"prefer-const": "error",
"no-console": ["warn", { allow: ["warn", "error", "info"] }],
"require-await": "error",
"no-throw-literal": "error",
"no-duplicate-imports": "error",
"no-shadow": "warn",
"consistent-return": "warn",
},
},
{ ignores: ["node_modules/", "coverage/", "dist/"] }, // objeto próprio
prettier,
];
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 100,
"arrowParens": "always",
"endOfLine": "lf"
}
// package.json
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"quality": "npm run format:check && npm run lint",
"prepare": "husky"
},
"lint-staged": {
"src/**/*.js": ["prettier --write", "eslint --fix", "eslint"],
"*.{json,md,yml}": ["prettier --write"]
}
}
ESLint para projetos com Express e Node
Regras adicionais recomendadas para back-end:
# O eslint-plugin-node foi abandonado e não funciona com o formato novo de
# configuração. O sucessor mantido é o eslint-plugin-n, com as mesmas regras.
npm install -D eslint-plugin-n
// eslint.config.js — adicione ao array
const nodePlugin = require("eslint-plugin-node");
module.exports = [
// ... configurações anteriores
{
plugins: { node: nodePlugin },
rules: {
"node/no-missing-require": "error", // require de módulo inexistente
"node/no-extraneous-require": "error", // require não listado no package.json
"node/no-unpublished-require": "warn", // require de devDependency em produção
"node/handle-callback-err": "error", // callback com erro não tratado
"node/no-process-exit": "warn", // process.exit() direto
},
},
];
Visualizando o impacto no projeto real
Antes de configurar ESLint e Prettier em um projeto real, você frequentemente encontra:
// Código típico sem padronização
var db = require('./config/db')
const Usuario = require('./models/usuario')
async function getUsuarios(req,res){
var usuarios = await Usuario.find()
if(usuarios == null){
res.status(404).send({erro: "não encontrado"})
}
else{
res.json(usuarios)
}
}
module.exports={getUsuarios}
Depois do ESLint + Prettier:
// Código após linting e formatação
const db = require('./config/db');
const Usuario = require('./models/usuario');
async function getUsuarios(req, res) {
const usuarios = await Usuario.find();
if (usuarios === null) {
return res.status(404).json({ erro: 'não encontrado' });
}
res.json(usuarios);
}
module.exports = { getUsuarios };
// ESLint ainda aponta: 'db' is assigned a value but never used
Tarefa para você
Configure ESLint e Prettier no projeto da API REST que construímos no Módulo 4:
# 1. Instale as dependências de desenvolvimento
# 2. Crie eslint.config.js com as regras recomendadas
# 3. Crie .prettierrc com suas preferências
# 4. Crie .editorconfig
# 5. Configure os scripts no package.json
# 6. Rode npm run lint no projeto e corrija TODOS os erros
# — não use eslint-disable para esconder, resolva de verdade
# 7. Rode npm run format e veja o que mudou
# 8. Configure o Husky para bloquear commits com erros
# 9. Crie uma regra customizada simples:
# Proíba o uso de "any" como nome de variável
# (evita conflito com TypeScript no futuro)
# Dica: "id-denylist": ["error", "any", "Number", "String"]
# 10. Teste o hook: tente commitar código com um console.log
# e veja o commit ser bloqueado
Ver solução — a configuração inteira — ESLint, Prettier, EditorConfig e o hook que bloqueia
// ---- eslint.config.js
// npm install -D eslint @eslint/js globals prettier eslint-config-prettier
// npm install -D husky lint-staged
//
// Config plana (flat config), o formato do ESLint 9 em diante. Não existe mais
// .eslintrc: a ordem do array é que manda, e o que vem depois sobrescreve.
const js = require("@eslint/js");
const globals = require("globals");
const prettier = require("eslint-config-prettier");
module.exports = [
{ ignores: ["node_modules/**", "coverage/**", "dist/**"] },
js.configs.recommended,
{
files: ["**/*.js"],
languageOptions: {
ecmaVersion: 2024,
sourceType: "commonjs",
globals: { ...globals.node },
},
rules: {
// console.log é rastro de depuração; error e warn são log de verdade.
"no-console": ["error", { allow: ["error", "warn"] }],
// O `next` do Express costuma sobrar na assinatura do error handler —
// e o handler PRECISA dos quatro parâmetros para o Express reconhecê-lo.
"no-unused-vars": ["error", { argsIgnorePattern: "^_|^next$" }],
eqeqeq: ["error", "always"],
"prefer-const": "error",
"no-var": "error",
// 9 — a regra pedida no enunciado, sem escrever plugin: id-denylist
// já vem no ESLint e recusa identificadores por nome.
"id-denylist": ["error", "any", "Number", "String", "data"],
},
},
{
// Os testes têm outro vocabulário global e podem imprimir à vontade.
files: ["testes/**/*.js", "**/*.test.js"],
languageOptions: { globals: { ...globals.node, ...globals.jest } },
rules: { "no-console": "off" },
},
// Sempre por último: desliga as regras de estilo que brigam com o Prettier.
prettier,
];
// ---- .prettierrc.json
// {
// "printWidth": 90,
// "singleQuote": false,
// "semi": true,
// "trailingComma": "es5",
// "arrowParens": "always",
// "endOfLine": "lf"
// }
//
// `endOfLine: "lf"` não é preciosismo: sem ele, um time misto Windows/Linux
// vê o diff inteiro mudar de dono a cada commit.
// ---- .editorconfig
// root = true
//
// [*]
// charset = utf-8
// indent_style = space
// indent_size = 2
// end_of_line = lf
// insert_final_newline = true
// trim_trailing_whitespace = true
//
// [*.md]
// trim_trailing_whitespace = false
//
// (dois espaços no fim da linha são quebra de linha em Markdown — aparar
// destruiria o texto)
// ---- .lintstagedrc.json
// {
// "*.js": ["eslint --fix --max-warnings=0", "prettier --write"],
// "*.{json,md,yml}": ["prettier --write"]
// }
//
// --------------------------------------------------------------
// 5 — os scripts do package.json
// --------------------------------------------------------------
//
// "scripts": {
// "lint": "eslint .",
// "lint:fix": "eslint . --fix",
// "format": "prettier --write .",
// "format:check": "prettier --check .",
// "test": "jest",
// "prepare": "husky"
// }
//
// O "prepare" é o que instala os hooks depois de um `npm install` — sem ele,
// quem clonar o projeto fica sem hook nenhum e não percebe.
//
// --------------------------------------------------------------
// 8 — o Husky
// --------------------------------------------------------------
//
// npx husky init
// echo 'npx lint-staged' > .husky/pre-commit
//
// .husky/pre-commit:
//
// npx lint-staged
//
// lint-staged roda só nos arquivos em stage. Rodar `eslint .` inteiro no hook
// transforma um commit de uma linha em quinze segundos de espera, e a pressa
// é o que faz o time descobrir o --no-verify.
//
// --------------------------------------------------------------
// 6 — `npm run lint`: a saída real, com as seis regras disparando
// --------------------------------------------------------------
//
// Arquivo-isca:
//
// var any = { titulo: "x" };
//
// function calcular(a, b) {
// console.log("debug", a);
// if (a == b) return 0;
// let total = a + b;
// return total;
// }
//
// src/exemplo-ruim.js
// 1:1 error Unexpected var, use let or const instead no-var
// 1:5 error Identifier 'any' is restricted id-denylist
// 1:5 error 'any' is assigned a value but never used no-unused-vars
// 4:3 error Unexpected console statement. Only these console
// methods are allowed: error, warn no-console
// 5:9 error Expected '===' and instead saw '==' eqeqeq
// 6:7 error 'total' is never reassigned. Use 'const' instead prefer-const
//
// ✖ 6 problems (6 errors, 0 warnings)
// 2 errors and 0 warnings potentially fixable with the `--fix` option.
//
// Resolver de verdade, como o enunciado exige: `const` no lugar de `var`,
// nome descritivo no lugar de `any`, `===`, e o console.log some — ele era
// depuração esquecida, não log.
//
// --------------------------------------------------------------
// 7 — `npm run format`: o que o Prettier muda
// --------------------------------------------------------------
//
// --- a/src/middlewares/erros.js
// +++ b/src/middlewares/erros.js
// - if (erro.name === "CastError") return res.status(400).json({ erro: `ID inválido` });
// + if (erro.name === "CastError")
// + return res.status(400).json({ erro: `ID inválido` });
//
// - res.status(status).json({ erro: status === 500 ? "Erro interno." : erro.message });
// + res
// + .status(status)
// + .json({ erro: status === 500 ? "Erro interno." : erro.message });
//
// Nada de semântica: só o printWidth de 90 sendo aplicado. É esse o acordo —
// o formato deixa de ser assunto de code review.
//
// --------------------------------------------------------------
// 10 — o hook bloqueando um console.log, de verdade
// --------------------------------------------------------------
//
// $ git add src/temp-debug.js
// $ git commit -m "feat: adiciona funcao somar"
//
// ✔ Backing up original state...
// ⋯ Running tasks for staged files...
// *.js — 1 file
// ✖ eslint --fix --max-warnings=0
// ↓ prettier --write
// ✖ Failed to run tasks for staged files!
// ⋯ Reverting to original state because of errors...
// ✔ Done reverting to original state!
//
// src/temp-debug.js
// 2:3 error Unexpected console statement. Only these console methods
// are allowed: error, warn no-console
//
// ✖ 1 problem (1 error, 0 warnings)
// husky - pre-commit script failed (code 1)
//
// Repare no "Reverting to original state": o lint-staged guarda o estado
// antes de rodar e devolve tudo se algo falhar. Sem isso, um `--fix` que
// quebrasse no meio deixaria metade dos arquivos alterados e o commit
// desfeito — o pior dos dois mundos.
Duas coisas que a configuração não faz, e é bom saber desde já. O hook é local: git commit --no-verify passa por cima dele, e quem clonar o repositório sem rodar npm install não tem hook nenhum — por isso o mesmo npm run lint precisa rodar no CI, que é onde a regra realmente vale. E o lint-staged não enxerga resolução de conflito: num commit de merge ele avisa could not find any staged files e não linta nada, justamente no commit em que alguém acabou de editar código à mão.
A divisão de trabalho é limpa: o Prettier decide como o código parece, o ESLint decide o que ele pode fazer. Misturar os dois é a origem de todo conflito de configuração, e o eslint-config-prettier existe apenas para desligar as regras de formatação do ESLint e encerrar a discussão. O ganho, porém, não está em nenhum dos dois isoladamente: está em rodá-los sozinhos, ao salvar e antes do commit — ferramenta de qualidade que depende de disciplina não é usada.
Fontes e Referências
- ESLint — Documentação: https://eslint.org/docs/latest
- ESLint — Rules reference: https://eslint.org/docs/latest/rules
- Prettier — Documentação: https://prettier.io/docs/en
- Prettier — Options: https://prettier.io/docs/en/options.html
- eslint-config-prettier: https://github.com/prettier/eslint-config-prettier
- Husky: https://typicode.github.io/husky
- lint-staged: https://github.com/okonet/lint-staged
- EditorConfig: https://editorconfig.org
- Clean Code — Robert C. Martin (Prentice Hall)
- roadmap.sh — Code Quality: https://roadmap.sh/best-practices/code-review
Exercícios
Exercício 1
Esta configuração roda, mas o ESLint continua analisando o node_modules e demorando uma eternidade. Por quê?
module.exports = [
js.configs.recommended,
{
languageOptions: { ecmaVersion: 2024, sourceType: "commonjs" },
rules: {
"no-unused-vars": "error",
"eqeqeq": ["error", "always"],
},
ignores: ["node_modules/", "coverage/", "dist/"],
},
];
Ver resposta
✓ Resposta: Porque o ignores está dentro de um objeto que também tem rules, e nessa posição ele significa outra coisa: "não aplique estas regras a estes arquivos". É um filtro local do bloco, não uma exclusão do projeto. Para valer globalmente, o ignores precisa estar sozinho num objeto seu — { ignores: ["node_modules/", "dist/"] } —, e é essa a convenção do formato novo de configuração. A diferença não gera erro nem aviso, o que a torna difícil de pegar: a única pista é o tempo de execução e algum aviso vindo de dentro de uma dependência. Vale acrescentar duas coisas sobre esse formato. A primeira é que o node_modules é ignorado por padrão pelo ESLint, então o sintoma clássico costuma ser outro — dist/ ou coverage/ sendo analisados, com centenas de erros em código gerado. A segunda é que o .eslintignore, que resolvia isso antigamente, deixou de existir no ESLint 9: quem migrou de versão e manteve o arquivo descobre que ele é simplesmente ignorado, sem aviso nenhum.
Exercício 2
Esta regra estava na configuração recomendada do artigo. Ela contradiz o que a série ensinou sobre async/await. Onde?
"no-return-await": "error",
// e o código que ela reprova:
async function buscar(id) {
try {
return await repositorio.buscar(id);
} catch (erro) {
registrar(erro);
throw new ErroDeNegocio("falha ao buscar");
}
}
Ver resposta
✓ Resposta: A regra manda remover o await do return por considerá-lo redundante — e dentro de um try ele não é. Sem o await, a função devolve a promise ao chamador antes de ela se assentar: o bloco try termina, a função sai de cena, e quando a rejeição chega já não há catch no caminho. O erro deixa de ser registrado e deixa de virar ErroDeNegocio; vira uma rejeição crua para quem chamou. Seguir o linter aqui introduz um bug silencioso, e é por isso que a própria equipe do ESLint descontinuou a no-return-await na versão 8.46. A lição é maior que o caso: linter não é autoridade, é opinião automatizada. Regras existem porque alguém julgou que um padrão costuma ser ruim, e "costuma" não é "sempre". Quando uma regra manda mudar código correto, as saídas são ajustar a configuração, não adotar a regra, ou — se ela vale em 95% dos casos — desativá-la na linha com um comentário que explique por quê. O que não se faz é obedecer no automático para ver a suíte ficar verde.
Exercício 3
O time discute há duas semanas se usa aspas simples ou duplas. Qual ferramenta encerra a discussão, e o que acontece se as duas tentarem opinar?
// eslint.config.js
rules: {
"quotes": ["error", "double"],
"semi": ["error", "always"],
"indent": ["error", 4],
}
// .prettierrc
{ "singleQuote": true, "semi": true, "tabWidth": 2 }
Ver resposta
✓ Resposta: Quem encerra a discussão é o Prettier, e o que está acima é a receita do conflito. O Prettier formata com aspas simples e dois espaços; o ESLint, logo em seguida, acusa erro exatamente no que o Prettier acabou de escrever. O resultado é o pior dos mundos: salvar o arquivo formata e depois sublinha tudo de vermelho, e o --fix de um desfaz o do outro num vaivém que trava o editor. A solução é a divisão de trabalho que dá nome ao artigo — o Prettier decide como o código parece, o ESLint decide o que ele pode fazer — e o instrumento que a garante é o eslint-config-prettier, que desliga de uma vez todas as regras de formatação do ESLint. Ele precisa ser o último item do array de configuração, porque quem vem depois sobrescreve quem veio antes; colocado no meio, as regras declaradas adiante voltam a valer e o conflito continua. Vale notar que quotes, semi e indent são justamente as regras que o ESLint moveu para fora do núcleo, por reconhecer que formatação não é problema dele.
Exercício 4
O hook de pré-commit está configurado assim. Um desenvolvedor commita e, no repositório, o arquivo aparece sem a formatação. O que faltou?
{
"lint-staged": {
"src/**/*.js": [
"prettier --write",
"eslint --fix"
]
}
}
Ver resposta
✓ Resposta: Na verdade não faltou nada — e é aqui que a pergunta engana. O lint-staged faz o git add dos arquivos modificados pelas tarefas automaticamente desde a versão 10, de modo que a formatação entra no commit. O problema real aparece em versões antigas, em que era preciso listar "git add" como última tarefa; quem copia configuração de tutorial antigo herda ou a linha desnecessária, ou o bug oposto. A pergunta serve para fixar o que de fato importa neste arranjo: a ordem das tarefas. O prettier --write precisa vir antes do eslint --fix, senão o ESLint corrige e o Prettier reformata por cima, podendo reintroduzir o que o outro acabou de arrumar. E há um detalhe que costuma surpreender: o lint-staged passa para as ferramentas apenas os arquivos em stage, não o projeto inteiro — o que o torna rápido, mas também significa que um arquivo não adicionado com problema passa sem ser visto. Por isso o hook não substitui a verificação completa no servidor de integração: ele é a primeira barreira, não a única.
Exercício 5
Este era o exemplo de regras para back-end no artigo. Ao rodar o ESLint, ele nem inicia. O que aconteceu?
const nodePlugin = require("eslint-plugin-node");
module.exports = [
{
plugins: { node: nodePlugin },
rules: {
"node/no-missing-require": "error",
"node/handle-callback-err": "error",
},
},
];
Ver resposta
✓ Resposta: O eslint-plugin-node está abandonado — sem manutenção desde 2021 — e não foi adaptado ao formato novo de configuração, então carregá-lo assim quebra na inicialização, geralmente com uma reclamação sobre o formato do objeto de plugin. O sucessor mantido é o eslint-plugin-n, que traz as mesmas regras sob o prefixo n/: n/no-missing-require, n/handle-callback-err, e assim por diante. A troca é quase mecânica — instalar o pacote novo e renomear o prefixo. O que vale reter vai além deste plugin: ferramenta de qualidade também envelhece, e um projeto que fixou as versões há três anos costuma ter um ou dois plugins mortos na configuração, cada um impedindo a atualização do ESLint. A verificação é barata e devia ser rotina: npm outdated mostra o atraso, e a página do pacote no registro avisa quando ele está descontinuado. Vale também desconfiar do sintoma "o linter parou de funcionar depois que atualizei" — quase sempre é um plugin que ficou para trás, não o ESLint.