


Alterar um Compilador que Gera JavaScript e Seu Sistema de Build, Testando Cada Camada
Para quando uma IA altera um monorepo de compilador que gera JavaScript. Ela compila pelo executor de tarefas, não mexe nas definições congeladas da árvore sintática, testa cada camada alterada, leva novas opções de build até o fim e mede a velocidade em vez de seguir o linter.
Para usar estas regras de IA
## Compile pelo Executor de Tarefas, Sem Pular Níveis
Use o executor de tarefas do repositório em vez de invocar manualmente as ferramentas de build subjacentes. O alvo padrão compila o toolchain da plataforma; alvos separados adicionam a biblioteca padrão, executam as suítes de teste, formatam o código e verificam a formatação sem reescrevê-la.
### Os alvos se apoiam uns nos outros
A instalação de dependências roda automaticamente para qualquer alvo que precise do ferramental JavaScript. O build padrão produz os binários do toolchain — tanto os executáveis do compilador quanto o binário separado do sistema de build — e os copia para o diretório de pacote específico da plataforma. O alvo da biblioteca padrão consome essas saídas do toolchain para compilar os fontes do runtime. Os alvos de teste reaproveitam tudo o que está acima deles.
### Não pule um nível
Como cada alvo depende do que está abaixo dele, invocar uma ferramenta de nível mais baixo diretamente pode deixar binários desatualizados no lugar e produzir resultados que não correspondem a um build limpo. Quando um resultado de build parecer impossível, execute o alvo de limpeza e recompile a partir do topo antes de investigar mais.
## Não Mexa na Árvore de Sintaxe Congelada; Atualize os Módulos de Conversão
Alguns arquivos são deliberadamente congelados porque extensões externas do parser dependem do formato exato deles. Nunca modifique a versão congelada da definição da árvore sintática. Quando a árvore sintática viva mudar, atualize em vez disso os dois módulos de mapeamento que convertem entre a representação viva e a congelada, para que as extensões continuem funcionando enquanto a árvore principal permanece livre para evoluir.
### A compatibilidade com o fork original não é uma restrição
O compilador começou como um fork de outro compilador, mas o projeto mantém a sua própria árvore sintática e pode fazer mudanças incompatíveis em relação ao original. Decida pelo que melhor serve a este alvo de compilação, não pelo que o projeto upstream faz.
### Evite reservar novas palavras-chave
Prefira uma implementação que não exija uma nova palavra reservada, já que reservar uma quebra código existente que já usa esse nome. Trate novas palavras-chave como último recurso e procure primeiro uma sintaxe que reutilize construções existentes.
## Corrija a Causa em Vez de Silenciar Avisos ou Usar Curingas Provisórios
Nunca use um atributo de supressão de avisos para calar o compilador. Corrija o problema de fundo — um aviso suprimido esconde exatamente a classe de erro que o aviso existe para pegar.
### Curingas são uma decisão, não um atalho
Não use padrões curinga como marcadores para novos recursos de linguagem que depois acabam esquecidos. Use um apenas quando tiver certeza de que o valor deve ser ignorado naquele caso específico, e confirme que cada novo recurso de linguagem é tratado por completo em todas as camadas do compilador.
### Descarte resultados explicitamente e propague o estado
Se uma função é chamada apenas por seus efeitos colaterais, use a função de ignorar explícita em vez de vincular o resultado a um curinga. Nunca descarte um resultado com estado — encaminhe-o pela cadeia de chamadas.
### Pequenas convenções que mantêm os padrões honestos
Não adicione um parâmetro unit final a uma função que já tem um argumento rotulado obrigatório; o rótulo já impede a aplicação parcial acidental. Omita o curinga final em padrões de registro quando o aviso que ele visa está desativado na base de código. Fique atento a construtores com nomes parecidos em representações intermediárias diferentes, porque o mesmo nome de variante pode significar coisas distintas em fases de compilação distintas.
## Descubra Qual Camada do Compilador Você Vai Alterar Antes de Editar
O pipeline transforma o texto-fonte em uma árvore sintática de superfície no parser, aplica transformações de frontend, produz uma árvore tipada no verificador de tipos, rebaixa para uma representação intermediária de nível lambda, converte para uma representação intermediária de nível JavaScript e, por fim, emite código. Identifique a camada que está editando antes de mexer em qualquer coisa.
### As camadas correspondem a diretórios
O diretório de sintaxe é dono do parsing e da sintaxe de superfície. O diretório de frontend é dono das transformações da árvore sintática e do processamento da interface de funções externas. O diretório do verificador de tipos é dono da verificação de tipos e das transformações da árvore tipada. O diretório central contém tanto os módulos de nível lambda quanto os de nível JavaScript, distinguidos por prefixo de arquivo. Diretórios separados guardam utilitários estendidos e estruturas de dados, a geração de definições de tipos para o ecossistema alvo, e o servidor de linguagem e o ferramental.
### Mudanças normalmente atravessam camadas
Um novo recurso de linguagem normalmente exige trabalho no parser, atualizações na definição da árvore sintática, suporte na verificação de tipos, tratamento no nível lambda, geração de JavaScript e testes, nessa ordem. Pular uma camada produz um recurso que faz parsing sem erros e depois compila errado.
## Depure Imprimindo a Representação Intermediária de Cada Etapa
O binário do compilador aceita flags que imprimem cada estágio: o fonte pré-processado, a árvore de parsing após o parsing, a árvore tipada após a verificação de tipos e a representação lambda não otimizada. Compare o despejo de cada estágio com o que você espera antes de começar a ler o fonte do compilador.
### Encaminhe o sintoma para a camada dona dele
Problemas de formatação da saída pertencem ao módulo de impressão formatada. Erros de tipo pertencem aos módulos do verificador de tipos. Resultados errados que aparecem só depois da otimização pertencem aos passes de análise de nível lambda. Código emitido malformado pertence aos módulos de geração de JavaScript.
### Reproduza em pequena escala
Compile um único arquivo mínimo em vez do projeto inteiro enquanto itera. Adicione impressões temporárias no módulo de impressão do lambda quando uma representação precisar ser inspecionada, e remova essa instrumentação antes que a mudança entre.
## Adicione Testes em Cada Camada Assim que Alterá-la
Adicione testes assim que modificar cada camada do compilador, não depois que todas as mudanças estiverem concluídas. Testar tarde esconde qual camada introduziu a regressão e transforma uma pequena bisseção em uma investigação da mudança inteira.
### Casos que sempre precisam de teste
Novos recursos de linguagem e correções de bugs sempre exigem testes. Modificar um passe de análise ou alterar a geração de JavaScript sempre exige testes. Sempre inspecione a qualidade do JavaScript emitido, não apenas sua correção.
### Cubra as suítes certas
Testes do parser validam a sintaxe. Testes de integração cobrem o comportamento de ponta a ponta. Testes unitários cobrem funções individuais do compilador. Testes de build cobrem casos de erro e casos-limite, incluindo erros de tipo em arquivo único e erros entre módulos que exigem arquivos de implementação e de interface separados. As suítes de ida e volta, de tipos gerados, de análise e de ferramental têm cada uma o seu próprio alvo e devem ser executadas quando a área correspondente mudar.
## Atualize o Catálogo de Erros e Avisos na Mesma Mudança
O repositório mantém um catálogo por módulo de todo erro e aviso que o compilador pode emitir, com cada entrada mapeada ou para uma fixture ou para um motivo documentado de ser inalcançável. O catálogo é a principal ferramenta para encontrar lacunas de cobertura e candidatos a remoção de código morto, então entradas desatualizadas dificultam as duas tarefas.
### Atualize o catálogo na mesma mudança do código
Quando adicionar ou remover uma variante de erro, adicione ou remova a linha dela na seção do módulo correspondente, defina o status como coberta, inalcançável ou ainda pendente, e ou vincule a fixture que a cobre ou registre por que a variante não pode ser alcançada. Quando adicionar ou remover uma fixture, atualize o caminho da fixture e o status da linha afetada para que o catálogo e a suíte de testes nunca se descolem.
## Mantenha a Compilação Rápida e o JavaScript Gerado Legível
O compilador foi projetado para ciclos rápidos de feedback e precisa escalar para bases de código grandes, e a saída gerada por ele é lida por pessoas. Ambas as propriedades são critérios de revisão, não algo secundário.
### Mantenha o código emitido legível
Evite emitir símbolos gerados sem sentido e mantenha a saída legível. Uma mudança que produz saída correta mas ilegível não está terminada.
### Meça antes e depois
Considere o impacto de cada mudança na velocidade de compilação, use os passes de otimização apropriados nos níveis lambda e JavaScript, e faça profiling antes e depois de qualquer mudança motivada por desempenho, em vez de raciocinar sobre isso a partir do código.
## Siga as Regras de Nomes e Registre no Changelog as Mudanças Visíveis
Siga a convenção de nomenclatura da linguagem em que estiver escrevendo: a linguagem de implementação do compilador usa snake case, enquanto os fontes escritos na própria linguagem compilada usam camel case. Siga os padrões existentes na base de código, prefira funções utilitárias existentes a reinventá-las, comente algoritmos complexos e lógica não óbvia, e preserve a compatibilidade retroativa sempre que for razoável fazê-lo.
### Commits carregam obrigações
Assine os commits no formato que o projeto exige, inclua os testes apropriados em toda mudança e confirme que o build passa antes de fazer o commit.
### Registre mudanças visíveis ao usuário
Adicione uma entrada no changelog para qualquer mudança visível ao usuário, seja uma correção de bug, um recurso ou uma mudança incompatível. Coloque-a na seção correspondente da versão não lançada atual e termine a linha com um link para a contribuição. Espera-se que as submissões incluam uma.
## Leve uma Nova Opção do Sistema de Build por Todos os Pontos que Ela Percorre
O sistema de build companheiro, escrito em uma linguagem de sistemas, oferece builds incrementais rápidos e melhor relato de erros. Seus módulos se dividem de forma limpa: estruturas de dados centrais como o estado do build e os registros de módulo, compilação e geração de argumentos do compilador, geração da árvore sintática e argumentos do parser, descoberta de pacotes e resolução de dependências, o grafo de dependências entre módulos e a detecção de ciclos, a limpeza de artefatos, o registro do build e o relato de erros, a interface de linha de comando, o parsing da configuração do projeto, o observador de arquivos e o ponto de entrada.
### Adicione a opção em todo lugar por onde ela passa
Declare a flag tanto no tipo de argumentos de build quanto no de watch e atualize a conversão entre eles. Estenda o parser de configuração para qualquer novo campo de configuração do projeto. Modifique os módulos de build relevantes. Depois propague o parâmetro por toda a cadeia, do ponto de entrada para a função de build, para a inicialização do build e para o estado do build, e também ao longo do caminho de watch. Adicione testes unitários para o novo comportamento.
### Defina precedência e tratamento de erros desde o início
Flags de linha de comando têm precedência sobre o arquivo de configuração do projeto. Propague erros pelo tipo de resultado padrão de encapsulamento de erros do projeto em vez de causar pânico, e use registro em nível de debug para diagnósticos de desenvolvimento.
## Rode o Sistema de Build Diretamente, Apontando para o Compilador Local
Execute os próprios comandos de toolchain do sistema de build a partir da raiz do repositório apontando para o arquivo de manifesto dele, do jeito que a integração contínua faz, em vez de entrar no diretório do subprojeto. O executor de tarefas também expõe atalhos para compilá-lo e para rodar os testes de integração dele.
### Aponte o binário de desenvolvimento para o toolchain local
Executado diretamente, o binário de outra forma acabaria usando uma versão instalada. Exporte as variáveis de ambiente do caminho do executável do compilador e do caminho do runtime, ambas resolvidas para caminhos absolutos dentro do checkout atual, antes de executá-lo. Note que o binário de desenvolvimento pode ter o nome do pacote em vez do nome da ferramenta, então verifique o nome do arquivo produzido em vez de supor.
### Aumente a verbosidade quando o comportamento não estiver claro
O sistema de build aceita flags de verbosidade crescente para registro em nível de info e em nível de debug. É assim que você vê detalhes como quais pastas uma sessão de watch está de fato observando.
### O tracing segue a especificação padrão
O tracing distribuído é configurado inteiramente pelas variáveis de ambiente padrão de telemetria — endpoint do coletor, sobrescrita de endpoint específica para traces, cabeçalhos do exportador, nome do serviço, atributos de recurso e nível de log — sem controles específicos do projeto. Definir um endpoint é o que habilita a telemetria; sem nenhum definido, o tracing não faz nada. Emita eventos pelas macros de tracing em vez das macros simples de log quando quiser que eles apareçam nos traces, porque as duas usam sistemas separados.
## Faça os Testes de CI Esperarem uma Condição, Não um Tempo Fixo
Os executores de integração contínua são mais lentos que as máquinas de desenvolvimento, o que torna esperas fixas frágeis. Faça polling da condição que realmente importa, como esperar um arquivo aparecer, em vez de dormir por uma duração adivinhada.
### Desligamento assíncrono não é conclusão
Um auxiliar que sinaliza a um observador para sair — por exemplo removendo um arquivo de lock — apenas envia o sinal; ele não espera o processo terminar. Evite disparar eventos de mudança de configuração logo antes de sair, porque o observador pode iniciar uma recompilação concorrente que você não esperava.
### Não faça edições de texto específicas de plataforma na mão
A edição de fluxo no próprio arquivo se comporta de forma diferente entre sistemas operacionais, então use os auxiliares de substituição e de normalização de caminhos do repositório em vez de invocações cruas de editores em testes de shell. Um script de integração individual pode ser executado sozinho exportando os caminhos do executável e do runtime, carregando o arquivo auxiliar compartilhado e invocando aquele script específico, o que é útil para iterar sem rodar a suíte inteira.
## Julgue as Sugestões do Linter pelo Desempenho Real
Sugestões do linter são insumo, não veredito. Quando uma refatoração sugerida puder prejudicar o desempenho ou a clareza, avalie o compromisso explicitamente em vez de aplicar a sugestão mecanicamente.
### Aceite complexidade só onde ela compensa
Uma struct de parâmetros não é automaticamente melhor do que vários argumentos; permita o lint localmente para funções que legitimamente precisam de muitos parâmetros e onde uma struct só acrescentaria cerimônia. Uma cópia é aceitável quando é pequena, única, exigida pelas regras de posse e fora de um caminho crítico — não faça engenharia excessiva ao redor dela.
### Otimize com base em evidência
Faça profiling antes de otimizar. A maior parte das preocupações de desempenho em um sistema de build é desprezível diante do tempo real de compilação. Onde a eficiência importa de fato, mantenha os tipos dos parâmetros consistentes ao longo de uma cadeia de chamadas em vez de converter entre tipos de string possuídos e emprestados em cada fronteira, já que essas conversões acrescentam alocações sem benefício algum. Recompile apenas os módulos sujos, paralelize trabalho independente, observe a pegada de memória do estado do build em projetos grandes e minimize as operações de sistema de arquivos.
