Por que analisar seu código? Por exemplo, para encontrar o console.log esquecido antes de confirmar. Mas e se você precisar alterar a assinatura da função em centenas de entradas no código? Expressões regulares lidarão aqui? Este artigo mostrará quais possibilidades as árvores de sintaxe abstrata oferecem a um desenvolvedor.

Abaixo do corte - uma transcrição em vídeo e texto de um relatório de Kirill Cherkashin (
z6Dabrata ) da conferência
HolyJS 2018 Piter .
Sobre o autor
Cyril nasceu em Moscou, agora vive em Nova York e trabalha na Firebase. Ensina Angular não apenas no Google, mas em todo o mundo. O organizador da maior mitap angular do mundo é o AngularNYC (assim como o VueNYC e o ReactNYC). Nas horas vagas em programação, ele gosta de tango, livros e conversas agradáveis.Serra ou madeira?
Vamos começar com um exemplo: digamos que você depurou um programa e enviou as alterações feitas no git, após o que você foi para a cama em silêncio. De manhã, seus colegas fizeram o download de suas alterações e, como você esqueceu de remover a saída de informações de depuração do console no dia anterior, ele as exibe e obstrui a saída. Muitos enfrentaram esse problema.
Existem ferramentas, como o
EsLint , para corrigir a situação, mas, para fins educacionais, vamos tentar encontrar uma solução por conta própria.
Qual ferramenta devo usar para remover todos os
console.log()
do código?
Escolhemos entre expressões regulares e o uso de árvores Abstract Sitax (ASD). Vamos tentar resolver isso com expressões regulares escrevendo alguma função
findConsoleLog
. Na entrada, ele receberá o código do programa como argumento e será exibido true se console.log () for encontrado em algum lugar no texto do programa.
function findConsoleLog(code) { return !!code.match(/console.log/); }
Escrevi 17 testes, tentando encontrar maneiras diferentes de quebrar nossa função. Esta lista está longe de estar completa.

O teste mais simples passou.
E se alguma função contiver a string "console.log" em seu nome?
function findConsoleLog(code) { return !!code.match(/\bconsole.log/); }
Adicionado um caractere que indica que
console.log
deve ocorrer no início da palavra.

Somente dois testes foram aprovados, mas e se
console.log
estiver no comentário e não precisar ser excluído?
Nós o reescrevemos para que o analisador não toque nos comentários.
function findConsoleLog(code) { return !!code .replace(/\/\/.*/) .match(/\bconsole.log/); }

Excluímos a remoção do "console.log" das linhas:
function findConsoleLog(code) { return !!code .replace(/\/\/.*|'.*'/, '') .match(/\bconsole.log/); }

Não esqueça que ainda temos espaços e outros caracteres que podem impedir a passagem de alguns testes:

Apesar de a idéia não ser tão simples, todos os 17 testes usando expressões regulares podem ser aprovados. Aqui, neste caso, o código da solução será:
function findConsoleLog(code) { return code .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//) .match(/\bconsole\s*.log\(/); }
O problema é que esse código não cobre todos os casos possíveis e é bastante difícil mantê-lo.
Considere como resolver esse problema usando o ASD.
Como as árvores são cultivadas?
A árvore de sintaxe abstrata é obtida como resultado do analisador trabalhando com o código do seu aplicativo. O analisador
@ babel / parser foi usado para demonstração
.Como exemplo, pegue a string
console.log('holy')
, passe-a pelo analisador.
import { parse } from 'babylon'; parse("console.log('holy')");
Como resultado de seu trabalho, um arquivo JSON de cerca de 300 linhas é obtido. Excluímos de suas linhas numéricas informações de serviço. Estamos interessados na seção do corpo. Meta-informação também não nos interessa. O resultado é de cerca de 100 linhas. Comparado à estrutura que o navegador gera para uma variável de corpo (cerca de 300 linhas), isso não é muito.
Vejamos alguns exemplos de como vários literais são representados no código em uma árvore de sintaxe:

Esta é uma expressão na qual existe Numeric Literal, um literal numérico.

A expressão já familiar do console.log. Tem um objeto que possui uma propriedade.

Se log for uma chamada de função, a descrição será a seguinte: existe uma expressão de chamada, ela possui argumentos - literais numéricos. Ao mesmo tempo, a expressão de chamada possui um nome - log.
Os literais podem ser diferentes: números, seqüências de caracteres, expressões regulares, booleano, nulo.
Voltar para a chamada console.log

Esta é uma expressão de chamada que possui Expressão de Membro dentro. A partir dele, fica claro que o objeto do console possui uma propriedade chamada log.
Desvio ASD
Agora vamos tentar trabalhar com essa estrutura no código. A biblioteca
babel-atravessar será usada para atravessar a árvore
.Os mesmos 17 testes são dados. Esse código é obtido analisando a árvore de sintaxe do programa e pesquisando entradas do "console.log":
function traverseConsoleLog(code, {babylon, babelTraverse, types, log}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path){ if ( path.node.property.type === 'Identifier' && path.node.property.name === 'log' && path.node.object.type === 'Identifier' && path.node.object.name === 'console' && path.parent.type === 'CallExpression' && path.Parentkey === 'callee' ) { hasConsoleLog = true; } } }) return hasConsoleLog; }
Vamos analisar o que está escrito aqui.
const ast = babylon.parse(code);
na variável ast, analisamos a árvore de sintaxe do código. Em seguida, fornecemos à biblioteca babel-parse essa árvore para processamento. Estamos procurando nós e propriedades com nomes correspondentes nas expressões de chamada. Defina a variável hasConsoleLog como true se a combinação necessária de nós e seus nomes for encontrada.
Podemos andar pela árvore, pegar os pais dos nós, descendentes, procurar quais argumentos e propriedades eles possuem, olhar os nomes dessas propriedades, tipos - isso é muito conveniente.
Há uma nuance desagradável que pode ser facilmente corrigida usando a biblioteca de tipos de babel. Para evitar erros ao pesquisar na árvore devido ao nome errado, por exemplo, em vez de
path.parent.type === 'CallExpression'
você acidentalmente escreveu
path.parent.type === 'callExpression'
, com tipos de babel você pode escrever assim :
Reescrevemos o código anterior usando babel-types:
function traverseConsoleLogSolved2(code, {babylon, babelTraverse, types}) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path) { if ( types.isIdentifier(path.node.object, { name: 'console'}) && types.isIdentifier(path.node.property, { name: 'log'}) && types.isCallExpression(path.parent) && path.parentKey === 'callee' ) { hasConsoleLog = true; } } }); return hasConsoleLog; }
Transformar ASD usando babel-traverse
Para reduzir os custos de mão-de-obra, precisamos que o
console.log
imediatamente removido do código - em vez de um sinal de que ele está no código.
Como precisamos remover não a MemberExpression em si, mas seu pai, no lugar
hasConsoleLog = true;
escrevemos
path.parentPath.remove();
.
Da função
removeConsoleLog
, ainda retornamos um valor booleano. Substituímos sua saída pelo código que irá gerar o gerador de babel, assim:
hasConsoleLog
=>
babelGenerator(ast).code
Babel-generator recebe a árvore de sintaxe abstrata modificada como parâmetro, retorna um objeto com a propriedade code, dentro desse objeto é gerado o código regenerado sem
console.log
. A propósito, se quisermos obter um mapa de código, podemos chamar a propriedade sourceMaps para este objeto.
E se você precisar encontrar um depurador?
Desta vez, usaremos o
ASTexplorer para concluir a tarefa. Depurador é um tipo de nó de instrução do depurador. Não precisamos olhar para toda a estrutura, já que esse é um tipo especial de nó, basta encontrar a instrução debugger. Escreveremos um plugin para o ESLint (no ASTexplorer).
O ASTexplorer foi projetado de maneira que você escreva o código à esquerda e à direita obtenha o ASD finalizado. Você pode escolher em qual formato deseja recebê-lo: JSON ou em formato de árvore.

Como usamos o ESLint, ele fará todo o trabalho de procurar arquivos para nós e nos fornecerá o arquivo necessário para que possamos encontrar a linha de depurador nele. Essa ferramenta usa um analisador ASD diferente. No entanto, existem vários tipos de ASDs em JavaScript. Algo que lembra o passado, quando diferentes navegadores implementaram a especificação de maneiras diferentes. Assim, implementamos a pesquisa do depurador:
export default function(context) { return { DebuggerStatement(node) { // , console.log path, - , path context.report(node, 'LOL Debugger!!!'); // ESLint , debugger, node , , debugger } } }
Verificando o trabalho de um plugin escrito:

Da mesma forma, você pode remover o depurador do código.
O que mais são ASD úteis
Pessoalmente, uso o ASD para simplificar o trabalho com estruturas Angular e outras estruturas front-end. Você pode importar, expandir, adicionar uma interface, método, decorador e qualquer outra coisa com o clique de um botão. Embora estejamos falando sobre Javascript, no entanto, o TypeScript também possui seus próprios ASDs, a única diferença é a diferença entre os nomes dos tipos de nós e a estrutura. No mesmo ASTExplorer pode ser selecionado como o idioma TypeScript.
Então:
- Temos mais controle sobre o código, refatoração mais fácil, modos de código. Por exemplo, antes de confirmar, você pode pressionar uma única tecla para formatar o código inteiro de acordo com as diretrizes. Codemods implica correspondência automática de código de acordo com a versão exigida da estrutura.
- Menos disputas sobre design de código.
- Você pode criar projetos de jogos. Por exemplo, forneça automaticamente ao programador feedback sobre o código que ele escreve.
- Melhor compreensão do JavaScript.
Alguns links úteis para Babel
- Todas as transformações do Babel usam esta API: plugins e presets .
- Parte do processo de adição de novas funcionalidades ao ECMAScript é a criação de um plug-in para o Babel. Isso é necessário para que as pessoas possam testar a nova funcionalidade. Se você seguir o link , poderá ver que da mesma maneira que os recursos do ASD são usados. Por exemplo, operador de atribuição lógica .
- O Babel Generator perde a formatação ao gerar código. Isso é parcialmente bom, porque, se essa ferramenta for usada na equipe de desenvolvimento, depois de gerar o código a partir do ASD, ela será a mesma para todos. Mas se você quiser manter sua formatação, poderá usar uma destas ferramentas: Reformulação ou Babel CodeMod .
- Nesse link, você pode encontrar muitas informações sobre Babel Awesome Babel .
- Babel é um projeto de código aberto e uma equipe de voluntários está trabalhando nele. Você pode ajudar. Há três maneiras de fazer isso: assistência financeira, você pode oferecer suporte ao site da patreon, com o qual Henry Zhu, um dos principais colaboradores da babel, trabalha, ajuda com o código em opencollective.com/babel .
Bônus
De que outra forma podemos encontrar nosso
console.log
no código? Use seu IDE! Usando a ferramenta de localização e substituição, depois de selecionar onde procurar o código.
A Intellij IDEA também possui uma ferramenta de "pesquisa estrutural" que pode ajudá-lo a encontrar os lugares certos no seu código, a propósito, ele usa um ASD.
De 24 a 25 de novembro, Kirill fará uma apresentação sobre dados binários JavaScript * LOVES * no Moscow HolyJS : desceremos para o nível de dados binários, cavaremos arquivos binários usando arquivos * .gif como exemplo e trataremos de estruturas de serialização como Protobuf ou Thrift. Após o relatório, será possível conversar com Cyril e discutir todas as questões de interesse na área de discussão.