Análise estática é qualquer verificação que uma ferramenta pode executar em seu código sem executá-lo - lendo o código-fonte, construindo um modelo dele e relatando problemas antes que uma única linha seja executada. Em um projeto Node.js, esse modelo se manifesta como três ferramentas distintas trabalhando juntas: um linter (ESLint) que raciocina sobre a estrutura e a correção do código, um formatador (Prettier) que raciocina sobre o layout e um verificador de tipos (tsc) que raciocina sobre as formas que fluem através do seu programa.
Esta página é o mapa antes do "como fazer". Noções Básicas de Linting configura um flat config em minutos, e Integração com Prettier, Verificação de Tipos em CI e Knip e Código Morto aprofundam-se em cada ferramenta. Aqui, o objetivo é entender por que os projetos Node buscam várias ferramentas específicas em vez de uma abrangente, e o que cada uma realmente está olhando quando é executada.
A análise estática em Node.js se divide em preocupações independentes - estilo/layout, correção estrutural e solidez de tipos - cada uma tratada por uma ferramenta de propósito específico em vez de um verificador que faz tudo.
Por que Importa: Confundir essas preocupações é exatamente o que produz ferramentas barulhentas e contraditórias - um linter lutando com um formatador sobre indentação, ou uma reclamação de "estilo" que na verdade esconde um bug real.
Conceitos Chave:árvore de sintaxe abstrata (AST), linting, formatação, verificação de tipos, severidade de regra, autofix.
Quando Usar: Configurar os portões de qualidade de um novo projeto, decidir qual ferramenta é responsável por qual classe de problema, depurar por que ESLint e Prettier parecem discordar, ou explicar a uma equipe por que "apenas executar o formatador" não resolve tudo.
Limitações / Trade-offs: A análise estática prova que a forma do código está correta, nunca que seu comportamento está correto - uma função perfeitamente lintada e perfeitamente tipada ainda pode retornar a resposta errada.
Tópicos Relacionados: Configuração plana do ESLint, Prettier, compilador do TypeScript, regras de limite de importação, detecção de código morto.
Toda ferramenta de análise estática começa da mesma maneira: ela analisa seu arquivo de código-fonte em uma árvore de sintaxe abstrata, uma representação estruturada da gramática do código em vez de seu texto bruto.
Uma vez que uma ferramenta tem essa árvore, ela pode fazer perguntas precisas que uma simples busca de texto nunca conseguiria - "esta variável é lida em algum lugar depois de ser atribuída?", "esta função tem um caminho de código sem return?", "esta promise é aguardada em algum lugar?". Uma verificação baseada em regex vê apenas caracteres; uma verificação baseada em AST vê significado.
A história da análise estática do Node se divide em três ferramentas porque cada uma está respondendo a uma pergunta genuinamente diferente sobre essa árvore:
Um formatador (Prettier) pergunta "como isso deve ser disposto na página" - indentação, comprimento da linha, estilo de aspas, vírgulas finais. Ele tem uma opinião sobre a aparência e nada mais.
Um linter (ESLint) pergunta "este código está estruturado corretamente?" - variáveis não utilizadas, ramos inacessíveis, padrões proibidos, direção de importação. Ele tem uma opinião sobre correção e convenção.
Um verificador de tipos (tsc) pergunta "os valores que fluem através deste programa correspondem às formas que me foi dito para esperar?" - ele tem uma opinião sobre solidez, verificada contra os tipos declarados em vez do comportamento em tempo de execução.
Uma maneira simples de separá-los: se uma alteração no código fosse invisível após executar git diff --ignore-all-space, isso é trabalho de formatação. Se a alteração altera o que o código faz sem alterar como ele se parece, isso é trabalho de linting ou verificação de tipos.
// Preocupação de formatação: espaçamento/aspas - Prettier cuida dissoconst x={a:1,b:2}// Preocupação de linting: variável não utilizada - ESLint cuida dissoconst unused = computeSomething();// Preocupação de verificação de tipos: forma incorreta - tsc cuida dissofunction total(price: number): number { return price; }total("19.99"); // string passada onde um número é necessário
A razão pela qual os projetos Node executam essas ferramentas separadamente - em vez de um verificador monolítico - se resume a quão diferentemente elas precisam funcionar.
A formatação deve ser um problema sem debate. O Prettier deliberadamente suporta poucas opções de configuração, porque o objetivo não é o estilo "correto", mas um estilo sobre o qual ninguém mais discute em revisões de código. Esse é um objetivo de design diferente de um linter, que deve ter dezenas de regras individualmente ativáveis porque as equipes legitimamente discordam sobre quais regras de correção são importantes para elas.
O linting é baseado em regras e em níveis de severidade. Cada regra do ESLint reporta em "off", "warn", ou "error", e as regras se compõem de várias fontes: eslint.configs.recommended para correção geral de JavaScript, as regras do typescript-eslint para padrões específicos de TypeScript, e regras de plugin como import-x/no-restricted-paths para restrições arquiteturais. Um arquivo de configuração plana é apenas um array ordenado desses conjuntos de regras, com entradas posteriores substituindo as anteriores para os arquivos que elas correspondem - que é também por que substituições específicas de arquivo (relaxando no-explicit-any apenas dentro de test/**) são um padrão normal e esperado, em vez de uma solução alternativa.
A verificação de tipos funciona em um eixo completamente diferente: não é um conjunto de regras configuráveis, é uma prova coerente. O tsc ou consegue verificar que o tipo declarado de cada valor corresponde a como ele é usado, ou não consegue - não há como "desligar este único erro de tipo" da maneira que você desativaria uma regra de lint, a menos que use uma saída explícita @ts-expect-error que precise ser justificada inline. É por isso que as equipes executam tsc --noEmit como seu próprio passo de CI: é um portão de aprovação/reprovação, não uma lista ajustável de avisos.
O autofix existe para dois dos três, e essa diferença importa operacionalmente. Toda a saída do Prettier é um autofix - não há "modo manual". O ESLint pode fazer autofix em um subconjunto de regras de forma determinística (eslint --fix), mas muitas regras (uma variável não utilizada que representa um bug real) exigem uma decisão humana. Erros de tipo nunca são corrigidos automaticamente - uma incompatibilidade de forma significa que a lógica do código precisa mudar, não sua sintaxe.
// eslint.config.js - cada entrada é uma camada, as posteriores vencem para os arquivos correspondentesexport default [ eslintRecommended, ...tseslintRecommended, { files: ["test/**/*.ts"], rules: { "@typescript-eslint/no-explicit-any": "off" } },];
A análise estática só tem força real quando é aplicada em algum lugar onde nem o hábito nem as extensões do editor podem ser ignorados - o que, na prática, significa CI, não a configuração local de um desenvolvedor. Um plugin de editor captura um problema para a pessoa que o tem instalado e prestando atenção; um portão de CI o captura para todos, todas as vezes, incluindo o PR de alguém que desativou acidentalmente sua extensão de linter.
É também onde os diferentes custos das três ferramentas se tornam um verdadeiro trade-off de engenharia, não apenas um filosófico. Verificações de formatação são essencialmente gratuitas - comparar a saída com uma forma canônica é rápido e paraleliza trivialmente. O linting simples é barato porque só precisa da AST de cada arquivo. Linting ciente de tipos - regras como "nenhuma promise flutuante" que precisam saber o tipo real de um valor, não apenas sua sintaxe - é significativamente mais lento, porque requer toda a maquinaria de verificação de tipos do TypeScript rodando sob o ESLint em vez de um parser leve. As equipes comumente limitam as regras cientes de tipos a src/** e as pulam para scripts ou código gerado especificamente para manter esse custo limitado.
Uma quarta categoria de análise estática fica ao lado dessas três e é fácil de perder: análise estrutural e de código morto - ferramentas como Knip ou dependency-cruiser que não verificam nenhum arquivo isoladamente, mas constroem um grafo de projeto inteiro para encontrar código que nada importa, dependências que nada usa, ou direções de importação que violam uma arquitetura pretendida. Isso ainda é análise estática (nada é executado), mas opera no nível do projeto em vez do nível do arquivo, que é por que é uma categoria de ferramenta genuinamente diferente do ESLint, embora os dois sejam configurados de forma semelhante.
Abordagem
Força
Fraqueza
Melhor Ajuste
Formatador (Prettier)
Elimina completamente o debate de estilo; custo de tempo de execução quase zero
Não tem opinião sobre correção alguma
Todo projeto, como uma linha de base inegociável
Linter (ESLint, sem consciência de tipos)
Rápido, captura bugs estruturais reais e desvios de convenção
Cego a incompatibilidades de tipos
Portão de correção padrão em cada arquivo
Linter ciente de tipos / tsc
Captura incompatibilidades de forma e manipulação insegura de any/promises
Notavelmente mais lento; precisa de um tsconfig funcional
Código de aplicação onde bugs de tipo em tempo de execução são caros
Análise de grafo em nível de projeto (Knip, dependency-cruiser)
Encontra código morto e violações arquiteturais que nenhuma ferramenta de arquivo único vê
Requer travessia de todo o projeto; mais configuração, falsos positivos ocasionais
Bases de código maiores e monorepos acumulando "cruft"
A tendência no ecossistema Node tem sido executar mais disso mais cedo e mais estritamente: a configuração plana substituiu o antigo cascade .eslintrc especificamente para tornar a composição de regras explícita em vez de implícita por caminhada de diretório, e políticas de CI de "zero avisos" (--max-warnings 0) tornaram-se comuns precisamente porque um aviso que ninguém é obrigado a corrigir é um aviso que ninguém lê.
"ESLint e Prettier são ferramentas concorrentes - escolha uma." Eles verificam coisas completamente diferentes; o modo de falha comum é uma regra de lint que também impõe estilo, o que entra em conflito com o formatador. A correção é desativar quaisquer regras de estilo do ESLint e deixar o Prettier ser o único responsável pelo layout.
"Se o linter passa, o código está correto." O linting prova que o código evita uma lista específica de padrões ruins conhecidos - ele não diz nada sobre se a lógica produz a resposta correta.
"A verificação de tipos é apenas uma forma mais rigorosa de linting." Eles são estruturalmente diferentes: linting é uma lista de regras independentemente configuráveis, verificação de tipos é uma prova coerente sobre todo o programa.
"Autofix significa que não preciso ler o diff."--fix é determinístico para regras mecânicas, mas também pode alterar silenciosamente o comportamento para regras com múltiplos fixes válidos - sempre revise um diff de autofix antes de confirmá-lo.
"Executar isso localmente é suficiente - lembrarei de corrigir os avisos." A aplicação apenas local é opcional por construção; apenas um portão de CI aplica a mesma regra a todos os contribuidores em todas as alterações.
Qual é a diferença real entre um linter e um formatador?
Um formatador apenas altera a aparência do código (espaçamento, aspas, quebras de linha) e nunca altera o comportamento. Um linter pode sinalizar - e às vezes corrigir - coisas que alteram o que o código realmente faz, como uma variável não utilizada ou um ramo inacessível.
Por que o ESLint precisa de uma AST em vez de apenas ler texto?
O texto sozinho não pode responder a perguntas estruturais como "esta variável é usada" ou "todos os caminhos de código retornam um valor". Uma AST dá à ferramenta um modelo real da gramática do código, para que ela possa raciocinar sobre as relações entre as partes do arquivo, não apenas caracteres.
Por que o Prettier tem deliberadamente poucas opções de configuração?
Seu propósito é acabar com os debates de estilo, produzindo um layout canônico para todos. Extensa configurabilidade recriaria o próprio debate que ele existe para remover.
A verificação de tipos é uma forma de linting?
Não realmente - um linter executa muitas regras independentemente configuráveis sobre a AST de um arquivo, enquanto um verificador de tipos prova uma coisa: que as formas de valor reais de um programa correspondem aos seus tipos declarados, e ele faz isso usando tanto a árvore de sintaxe quanto as informações de tipo de arquivos importados.
Por que o linting ciente de tipos é mais lento que o linting regular?
Regras cientes de tipos precisam das informações de tipo completas do compilador TypeScript, não apenas da AST de um arquivo, então o ESLint precisa construir e consultar esse modelo de projeto inteiro antes de poder avaliar a regra.
O autofix pode quebrar meu código?
Para a maioria das regras mecânicas, não - o fix é uma transformação segura e determinística. Mas algumas regras têm mais de um fix válido, e aplicar um automaticamente pode alterar o comportamento de uma maneira que um revisor humano teria percebido, então a saída do autofix ainda deve ser revisada.
Por que executar a verificação de tipos como um passo de CI separado em vez de dentro do ESLint?
Porque é um tipo diferente de verificação com um perfil de custo diferente - uma prova de aprovação/reprovação sobre todo o programa em vez de um conjunto ajustável de regras com escopo de arquivo - então a maioria das equipes o isola (tsc --noEmit) para manter seu custo e seu sinal fáceis de raciocinar por conta própria.
O que "estática" em análise estática realmente significa?
Que a verificação ocorre sem executar o programa - a ferramenta apenas lê e raciocina sobre o código-fonte, em oposição à análise dinâmica (testes, profilers) que observa o código enquanto ele é executado.
A detecção de código morto é linting?
É análise estática, mas em um escopo diferente - um linter raciocina sobre a AST de um arquivo, enquanto ferramentas de código morto e de grafo de dependência como Knip raciocinam sobre o grafo de importação de todo o projeto para encontrar código ou dependências que nada usa.
Por que as equipes impõem "zero avisos" em vez de apenas corrigir erros?
Um aviso deixado sem resolução treina todos a ignorar avisos, incluindo o próximo que importa. Tratar avisos como falhas de compilação mantém o sinal significativo.
A análise estática substitui testes?
Não - ela prova que a forma do código está correta (sem código não utilizado, tipos corretos, sem padrões proibidos), nunca que seu comportamento corresponde à intenção. Uma função bem tipada e limpa de lint ainda pode computar o resultado errado, o que apenas um teste pode capturar.
Por que a configuração plana é importante para como essas ferramentas se compõem?
A configuração plana representa conjuntos de regras como um array explícito e ordenado onde entradas posteriores substituem as anteriores para os arquivos que elas correspondem, substituindo o antigo modelo de cascata implícita de diretório - tornando claro exatamente quais regras se aplicam a quais arquivos em vez de depender de como os arquivos .eslintrc se aninhavam.