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": "error", // return await desnecessário
"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: ["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-unused-vars": "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 (alternativa ao campo ignores no config)
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"] }],
"no-return-await": "error",
"require-await": "error",
"no-throw-literal": "error",
"no-duplicate-imports": "error",
"no-shadow": "warn",
"consistent-return": "warn",
},
ignores: ["node_modules/", "coverage/", "dist/"],
},
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:
npm install -D eslint-plugin-node
// 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
Conclusão
Neste artigo você aprendeu:
- A diferença fundamental entre ESLint (qualidade) e Prettier (formatação)
- Como instalar e configurar o ESLint com regras essenciais
- Os três níveis de severidade de regra — off, warn, error
- Como instalar e configurar o Prettier com as opções mais comuns
- Como integrar os dois sem conflitos usando eslint-config-prettier
- Como automatizar com scripts no package.json
- Como configurar o VS Code para formatar ao salvar
- Como usar Husky e lint-staged para validar código antes do commit
- O EditorConfig para padronização entre diferentes editores
- Uma configuração completa e pronta para projetos Node.js
No próximo artigo vamos aprender sobre Git e fluxo de trabalho profissional — como usar controle de versão de forma estratégica em projetos em equipe.
📚 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