Back

Por Dentro da AST: Como as Ferramentas Entendem Código

Por Dentro da AST: Como as Ferramentas Entendem Código

Toda vez que o ESLint sinaliza um problema antes de você executar seu código, ou o Prettier reformata uma função bagunçada no momento em que você salva, algo preciso está acontecendo nos bastidores. Essas ferramentas não estão lendo seu código-fonte como texto. Elas estão lendo-o como uma árvore estruturada. Compreender essa árvore — a Abstract Syntax Tree (AST) — explica como praticamente todas as ferramentas modernas de desenvolvimento funcionam.

Principais Conclusões

  • Uma AST é uma representação em forma de árvore da estrutura do seu código, frequentemente despojada de detalhes superficiais como espaços em branco e parênteses de agrupamento.
  • Linters, formatadores e compiladores funcionam analisando o código-fonte em uma AST, percorrendo-a e aplicando regras ou transformações em cada nó.
  • O padrão visitor é a abordagem dominante: as ferramentas registram manipuladores para tipos específicos de nós e reagem conforme a árvore é percorrida.
  • Uma Concrete Syntax Tree (CST) retém todos os tokens, tornando-a mais adequada para editores que precisam de análise incremental e representações de alta fidelidade.
  • Ferramentas baseadas em Rust como Biome e Oxc usam o mesmo modelo parse-traverse-act, mas elevam o desempenho significativamente além do que muitos parsers baseados em JavaScript alcançam.

O Que É uma Abstract Syntax Tree?

Quando uma ferramenta analisa seu código-fonte, ela passa por dois estágios.

Primeiro, um lexer (ou tokenizador) divide o texto bruto em tokens: palavras-chave, identificadores, operadores, pontuação. Em seguida, um parser pega esses tokens e constrói uma árvore que representa a estrutura gramatical do seu código.

Essa árvore é a AST. Ela é chamada de “abstrata” porque frequentemente descarta detalhes superficiais — espaços em branco, a maioria da pontuação e parênteses usados puramente para agrupamento — mantendo apenas o que é semanticamente significativo.

Considere esta linha:

const x = 5 + 3

Um parser produz algo assim:

{
  "type": "VariableDeclaration",
  "kind": "const",
  "declarations": [{
    "type": "VariableDeclarator",
    "id": { "type": "Identifier", "name": "x" },
    "init": {
      "type": "BinaryExpression",
      "operator": "+",
      "left":  { "type": "Literal", "value": 5 },
      "right": { "type": "Literal", "value": 3 }
    }
  }]
}

Cada nó tem um type. Nós filhos se aninham dentro de nós pais. O programa inteiro se torna uma árvore enraizada em um nó Program.

Você pode explorar isso diretamente usando o AST Explorer, que permite colar qualquer código JavaScript ou TypeScript e inspecionar a árvore resultante em tempo real.

Como Linters e Formatadores Usam ASTs

Uma vez que uma ferramenta tem a AST, ela pode fazer algo útil: percorrê-la.

A maioria das ferramentas usa o padrão visitor. Você registra uma função para um tipo específico de nó, e o mecanismo de travessia chama essa função toda vez que encontra um nó correspondente.

O ESLint funciona exatamente dessa maneira. Cada regra de lint é um objeto visitor. Quando o ESLint percorre a AST, ele chama os manipuladores de regras relevantes em cada nó. Uma regra que proíbe == em favor de === simplesmente escuta nós BinaryExpression e verifica a propriedade operator.

O Babel usa a mesma abordagem para transformação de código. Ele analisa o código-fonte em uma AST, aplica transformações baseadas em visitors que modificam nós e então imprime a árvore modificada de volta para código-fonte. É assim que ele compila sintaxe JavaScript moderna em equivalentes mais antigos.

O Prettier adota um ângulo diferente. Ele analisa o código em uma AST, descarta toda a formatação original e reimprime a árvore de acordo com suas próprias regras de layout. A AST é a fonte da verdade — não o texto original.

AST vs. CST: Quando a Estrutura Importa Mais

Uma AST omite tokens que são sintaticamente necessários, mas semanticamente redundantes. Uma Concrete Syntax Tree (CST) mantém a estrutura sintática completa do código-fonte, retendo tokens e suas posições.

O Tree-sitter produz uma árvore de sintaxe concreta otimizada para análise incremental. Como ele pode atualizar apenas a porção da árvore afetada por uma edição, é bem adequado para realce de sintaxe, dobramento de código e edição estrutural dentro de editores modernos como Neovim e Zed.

A Mudança em Direção à Análise de Alto Desempenho

Ferramentas mais recentes no ecossistema JavaScript estão levando o desempenho de análise significativamente mais longe. Projetos como Biome e Oxc são implementados em Rust e constroem seus próprios parsers e representações AST do zero.

Eles lidam com linting e formatação em velocidades que frequentemente superam parsers baseados em JavaScript, enquanto suportam sintaxe moderna como atributos de importação e recursos recentes do TypeScript.

O modelo subjacente é o mesmo — analisar para uma árvore, percorrê-la, aplicar análise ou transformação — mas a implementação é otimizada para escala.

Conclusão

Seja você escrevendo uma regra customizada do ESLint, construindo um codemod com jscodeshift, ou apenas tentando entender por que uma ferramenta se comporta de determinada maneira, a AST é o lugar certo para procurar. É a representação estruturada sobre a qual toda ferramenta séria de desenvolvimento é construída. Uma vez que você consegue ler uma árvore de sintaxe, o comportamento de linters, formatadores e compiladores deixa de parecer mágica.

Perguntas Frequentes

Não para uso cotidiano. Essas ferramentas funcionam imediatamente com padrões sensatos. Mas se você quiser escrever regras de lint personalizadas, construir codemods ou depurar comportamentos inesperados de ferramentas, entender como as ASTs representam seu código lhe dá um modelo mental claro do que a ferramenta está realmente fazendo em cada etapa.

Uma AST remove tokens sintaticamente necessários, mas semanticamente redundantes, como vírgulas, colchetes e espaços em branco. Uma CST retém a estrutura sintática completa do código-fonte. CSTs são preferidas em editores e IDEs onde representação de alta fidelidade e análise incremental importam, enquanto ASTs são tipicamente usadas por linters, compiladores e formatadores.

A maneira mais fácil é o AST Explorer em astexplorer.net. Cole qualquer trecho de JavaScript ou TypeScript, escolha um parser como acorn, babel ou typescript, e a ferramenta exibe a árvore completa em tempo real. Ela também suporta outras linguagens e permite experimentar com transformações diretamente no navegador.

Rust dá a essas ferramentas controle direto sobre alocação e layout de memória, evita pausas de coleta de lixo e compila para código nativo altamente otimizado. Isso significa que análise, travessia e análise executam significativamente mais rápido, o que se torna especialmente perceptível em bases de código grandes com milhares de arquivos.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay