ESLint e Prettier: código limpo e padronizado

[116] 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 Referências

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

Trabalhando com datas e horas em JavaScript
Trabalhando com datas e horas em JavaScript

Data parece o tipo mais simples que existe e é o que mais engana: o mês começa…

Introdução ao TypeScript
Introdução ao TypeScript

Uma variável que é número agora e string depois dá flexibilidade e gera uma…

Projeto: API REST com autenticação JWT
Projeto: API REST com autenticação JWT

O Módulo 4 fecha juntando Node, Express, MongoDB e Mongoose numa API de…