Você salva um arquivo, o Prettier reformata, e o ESLint aponta imediatamente um erro na linha que o Prettier acabou de tocar. As duas ferramentas estão configuradas, as duas estão rodando, e em vez de ajudar, estão brigando uma com a outra.

As duas ferramentas fazem trabalhos diferentes. O problema é que as duas têm opinião sobre uma única coisa: a aparência do código na tela.

O que o ESLint realmente verifica

O ESLint analisa seu código em uma árvore de sintaxe abstrata e executa regras sobre a estrutura dela. A maior parte do seu valor vem de regras que encontram bugs reais: uma variável nunca definida, um return inalcançável depois de outro return, um import que ficou sem uso depois de uma refatoração, uma chave duplicada em um objeto que sobrescreve a primeira em silêncio. São verificações semânticas: o ESLint lê o que o código faz.

O ESLint também traz regras estilísticas, como exigir aspas simples ou ponto e vírgula e limitar o tamanho da linha — embora desde a versão 8.53 elas estejam obsoletas em favor do projeto separado ESLint Stylistic, e mesmo assim não detectam bugs. Elas capturam divergências de estilo, e é justamente essa parte que colide com o Prettier.

O que o Prettier realmente faz

O Prettier não lê seu código para entender o significado. Ele analisa, descarta a formatação original e reimprime o arquivo inteiro seguindo suas próprias regras de indentação, tamanho de linha, estilo de aspas e pontos de quebra para linhas longas. Execute-o duas vezes sobre a mesma entrada e você obtém uma saída idêntica byte a byte — esse é o objetivo. Quase não há nada para configurar, e isso é de propósito: um .prettierrc, um punhado de opções, e todo arquivo do projeto fica com a mesma cara, não importa quem escreveu.

Execute prettier --check . na CI e ele diz se cada arquivo corresponde a essa formatação canônica. Ele não sabe e não se importa se o código funciona.

Por que ESLint e Prettier entram em conflito

A sobreposição é pequena. As regras estilísticas do ESLint (aspas, ponto e vírgula, indentação, vírgulas finais) cobrem o mesmo território do Prettier, e os dois nem sempre concordam. Quando não concordam, surge o ciclo do primeiro parágrafo: o Prettier reformata uma linha, o ESLint aponta o resultado, você “corrige” na mão, o Prettier devolve como estava.

Então pare de pedir ao ESLint que tome decisões de formatação. Deixe que ele faça só o que o Prettier não pode: encontrar bugs e padrões ruins. O eslint-config-prettier é uma configuração que desativa toda regra do ESLint que poderia conflitar ou duplicar a saída do Prettier. Não adiciona nada. Só desativa.

Um segundo pacote, o eslint-plugin-prettier, vai no sentido contrário: ele executa o Prettier como regra do ESLint, então cada diferença de formatação aparece como um erro próprio do ESLint. A própria documentação do Prettier chama essa configuração de “geralmente não recomendada”. É mais lento, porque o ESLint invoca o Prettier arquivo por arquivo. Também é mais ruidoso: uma única reformatação pode se transformar em dezenas de erros do ESLint em vez de um diff só. Execute o Prettier por conta própria.

Como configurar ESLint e Prettier com flat config

Desde o ESLint 9, o arquivo de configuração é eslint.config.js, um array plano de objetos de configuração que substitui o antigo formato .eslintrc. Se você está começando um projeto do zero:

1
npm init @eslint/config@latest

Isso faz algumas perguntas e escreve um eslint.config.js. Depois adicione o Prettier e a configuração que silencia as regras conflitantes:

1
npm install --save-dev --save-exact prettier eslint-config-prettier

O eslint-config-prettier traz um ponto de entrada para flat config. Importe-o e coloque-o como último item do array. Entradas posteriores sobrescrevem as anteriores, então qualquer coisa colocada depois pode reativar uma regra que tinha sido desligada:

1
2
3
4
5
6
7
8
// eslint.config.js
import js from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";

export default [
  js.configs.recommended,
  eslintConfigPrettier,
];

Adicione um .prettierrc para as opções próprias do Prettier (a maioria dos times não precisa de quase nenhuma):

1
2
3
4
{
  "semi": true,
  "singleQuote": true
}

E dois scripts no package.json para as duas ferramentas rodarem de forma independente:

1
2
3
4
5
6
{
  "scripts": {
    "lint": "eslint .",
    "format": "prettier --write ."
  }
}

O eslint-config-prettier também traz uma verificação de linha de comando que lista qualquer regra da sua configuração que ainda conflite com o Prettier:

1
npx eslint-config-prettier eslint.config.js

Código de saída 0 significa nenhum conflito. 2 significa que encontrou algum, e a saída nomeia cada regra para você remover.

Usando eslint-config-prettier com typescript-eslint

Para TypeScript, o typescript-eslint se conecta ao mesmo array de flat config e adiciona suas próprias regras semânticas por cima do ESLint puro — tseslint.configs.recommended proíbe o any escrito direto no seu código (no-explicit-any). Capturar um any que vazou de uma chamada a uma biblioteca sem tipagem é um passo separado e mais pesado: troque para recommendedTypeChecked, que precisa de parserOptions.project apontando para o seu tsconfig.json, para que as regras possam pedir os tipos reais ao compilador do TypeScript. A regra de ordem não muda: o eslint-config-prettier sempre vai por último.

1
2
3
4
5
6
7
8
9
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier/flat";

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  eslintConfigPrettier,
];

Nenhuma das duas ferramentas verifica se seus tipos foram bem projetados. Um generic que devia ter sido restringido com extends passa pelos dois controles; isso é trabalho do compilador, tema tratado em Generics em TypeScript Explicados com Exemplos.

Executando ESLint e Prettier no editor e na CI

No editor, ative “format on save” para executar o Prettier, e deixe o ESLint reportar separadamente, ao salvar ou em um hook de pre-commit. Depois que os arquivos de configuração acima existirem, as extensões do editor para as duas ferramentas funcionam de forma independente. Não é preciso ligar uma à outra.

Na CI, execute as duas como etapas separadas:

1
2
npx eslint .
npx prettier --check .

Um prettier --check que falha significa que alguém não executou o formatador. Um eslint que falha significa que o código tem um problema real. Duas etapas mantêm essa diferença legível no log da CI.

Erros comuns na configuração de ESLint e Prettier

O mais comum é instalar o eslint-plugin-prettier porque um tutorial antigo recomenda, e depois se perguntar por que o eslint . está lento e a lista de problemas é o triplo do esperado. Remova o plugin, mantenha o eslint-config-prettier, execute o Prettier por conta própria.

O segundo é a ordem. Se uma configuração compartilhada ou um override específico do projeto fica depois de eslintConfigPrettier no array, ele reativa em silêncio uma regra que tinha sido desligada. A verificação de linha de comando acima pega isso antes que chegue a um PR.

O terceiro é o escopo. Essa dupla foi feita para JavaScript e TypeScript. Se você está começando um projeto novo e prefere uma ferramenta única e mais rápida para os dois trabalhos, dê uma olhada no Biome. É um compromisso diferente, não um substituto direto da configuração acima.

O que fazer hoje no seu projeto

Abra seu package.json. Se o eslint-plugin-prettier estiver lá, desinstale. Adicione o eslint-config-prettier como última entrada em eslint.config.js, execute npx eslint-config-prettier eslint.config.js e corrija o que ele apontar. Depois disso, todo erro de ESLint que você vir é sobre algo que o ESLint encontrou na estrutura do código, não uma linha que o Prettier reformataria do mesmo jeito. Ele ainda não pega tudo: esquecer await dentro de um loop ou deixar uma promise rejection sem tratamento é um erro de lógica, não um erro de lint, a menos que você adicione um plugin para isso — veja Async/Await e o Event Loop do JavaScript Explicados para ver como esses erros aparecem.

Artigos relacionados