ESLint e Prettier: código limpo e padronizado

ESLint e Prettier: código limpo e padronizado

Entrar num projeto em que cada arquivo tem um estilo custa caro: revisão que discute vírgula em vez de lógica e diff enorme por reformatação. A divisão aqui é clara — o Prettier decide como o código parece, o ESLint decide o que ele pode fazer — e as duas ferramentas rodam sozinhas, ao salvar e antes do commit.
Javascript

20 min de leitura

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 — Prettier
  • dbaeumer.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 leituras recomendadas

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.

Comentários

Mais em Javascript

Formulários: validação e coleta de dados
Formulários: validação e coleta de dados

Um campo de senha e outro de confirmação já mostram o limite do HTML sozinho…

Tratamento de erros em requisições HTTP
Tratamento de erros em requisições HTTP

Código que só funciona quando tudo dá certo não está pronto para produção…

Laços de Repetição: for, while e do...while
Laços de Repetição: for, while e do...while

Esquecer o incremento dentro do while congela a aba, e trocar um sinal na…