Pourquoi analyser votre code? Par exemple, afin de trouver le fichier console.log oublié avant de valider. Mais que faire si vous devez modifier la signature de la fonction en centaines d'entrées dans le code? Les expressions régulières vont-elles y faire face? Cet article vous montrera quelles possibilités les arbres de syntaxe abstraite offrent à un développeur.

Under the cut - une vidéo et une transcription textuelle d'un rapport de Kirill Cherkashin (
z6Dabrata ) de la conférence
HolyJS 2018 Piter .
À propos de l'auteur
Cyril est né à Moscou, vit maintenant à New York et travaille chez Firebase. Enseigne Angular non seulement chez Google, mais partout dans le monde. L'organisateur du plus grand mitap angulaire au monde est AngularNYC (ainsi que VueNYC et ReactNYC). Dans ses temps libres consacrés à la programmation, il aime le tango, les livres et les conversations agréables.Scie à métaux ou bois?
Commençons par un exemple: disons que vous avez débogué un programme et envoyé les modifications apportées à git, après quoi vous vous êtes couché tranquillement. Le matin, il s'est avéré que vos collègues ont téléchargé vos modifications et, puisque vous avez oublié de supprimer la sortie des informations de débogage vers la console la veille, il l'affiche et bouche la sortie. Beaucoup ont été confrontés à ce problème.
Il existe des outils, comme
EsLint , pour corriger la situation, mais à des fins éducatives, essayons de trouver une solution par nous-mêmes.
Quel outil dois-je utiliser pour supprimer tous les
console.log()
du code?
Nous choisissons entre les expressions régulières et l'utilisation d'arbres Sitax abstraits (ASD). Essayons de résoudre ce problème avec des expressions régulières en écrivant une fonction
findConsoleLog
. À l'entrée, il recevra le code du programme en tant qu'argument et affichera true si console.log () se trouve quelque part dans le texte du programme.
function findConsoleLog(code) { return !!code.match(/console.log/); }
J'ai écrit 17 tests, essayant de trouver différentes façons de briser notre fonction. Cette liste est loin d'être complète.

Le test le plus simple a réussi.
Et si une fonction contient la chaîne «console.log» dans son nom?
function findConsoleLog(code) { return !!code.match(/\bconsole.log/); }
Ajout d'un caractère qui indique que
console.log
doit apparaître au début du mot.

Seuls deux tests ont réussi, mais que faire si
console.log
est dans le commentaire et n'a pas besoin d'être supprimé?
Nous le réécrivons pour que l'analyseur ne touche pas les commentaires.
function findConsoleLog(code) { return !!code .replace(/\/\/.*/) .match(/\bconsole.log/); }

Nous excluons la suppression de "console.log" des lignes:
function findConsoleLog(code) { return !!code .replace(/\/\/.*|'.*'/, '') .match(/\bconsole.log/); }

N'oubliez pas que nous avons encore des espaces et d'autres caractères qui peuvent empêcher certains tests de passer:

Malgré le fait que l'idée n'était pas assez simple, les 17 tests utilisant des expressions régulières peuvent être réussis. Ici donc, dans ce cas, le code de la solution ressemblera:
function findConsoleLog(code) { return code .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//) .match(/\bconsole\s*.log\(/); }
Le problème est que ce code ne couvre pas tous les cas possibles, et il est assez difficile à maintenir.
Considérez comment résoudre ce problème en utilisant ASD.
Comment les arbres poussent-ils?
L'arborescence de syntaxe abstraite est obtenue grâce à l'analyseur syntaxique travaillant avec le code de votre application. L'analyseur
@ babel / parser a été utilisé pour la démonstration
.À titre d'exemple, prenez la chaîne
console.log('holy')
, passez-la dans l'analyseur.
import { parse } from 'babylon'; parse("console.log('holy')");
Grâce à son travail, un fichier JSON d'environ 300 lignes est obtenu. Nous excluons de leurs lignes numériques les informations de service. Nous nous intéressons à la section corps. La méta-information ne nous intéresse pas non plus. Le résultat est d'environ 100 lignes. Comparé à la structure que le navigateur génère pour une variable de corps (environ 300 lignes), ce n'est pas beaucoup.
Examinons quelques exemples de la façon dont divers littéraux sont représentés dans le code dans un arbre de syntaxe:

C'est une expression dans laquelle il y a Numeric Literal, un littéral numérique.

L'expression console.log déjà familière. Il a un objet qui a une propriété.

Si log est un appel de fonction, la description est la suivante: il existe une expression d'appel, elle a des arguments - des littéraux numériques. En même temps, l'expression appelante a un journal de noms.
Les littéraux peuvent être différents: nombres, chaînes, expressions régulières, booléens, null.
Retour à l'appel console.log

Il s'agit d'une expression d'appel qui contient une expression de membre. De là, il est clair que l'objet console à l'intérieur possède une propriété appelée log.
Contournement ASD
Essayons maintenant de travailler avec cette structure dans le code. La bibliothèque
babel-traverse sera utilisée pour parcourir l'arbre
.Les mêmes 17 tests sont donnés. Un tel code est obtenu en analysant l'arbre de syntaxe du programme et en recherchant les entrées de "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; }
Analysons ce qui est écrit ici.
const ast = babylon.parse(code);
dans la variable ast, nous analysons l'arbre de syntaxe du code. Ensuite, nous donnons à la bibliothèque babel-parse cet arbre pour le traitement. Nous recherchons des nœuds et des propriétés avec des noms correspondants dans les expressions d'appel. Définissez la variable hasConsoleLog sur true si la combinaison requise de nœuds et de leurs noms est trouvée.
Nous pouvons nous déplacer dans l'arbre, prendre les parents des nœuds, des descendants, chercher quels arguments et propriétés ils ont, regarder les noms de ces propriétés, types - c'est très pratique.
Il y a une nuance désagréable qui peut être facilement corrigée en utilisant la bibliothèque babel-types. Afin d'éviter les erreurs lors de la recherche dans l'arborescence en raison d'un nom incorrect, par exemple, au lieu de
path.parent.type === 'CallExpression'
vous avez accidentellement écrit
path.parent.type === 'callExpression'
, avec les types babel, vous pouvez écrire comme ceci :
Nous réécrivons le code précédent à l'aide de 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; }
Transformer l'ASD en utilisant babel-traverse
Pour réduire les coûts de main-d'œuvre, nous avons besoin que
console.log
immédiatement supprimé du code - au lieu d'un signal indiquant qu'il se trouve dans le code.
Puisque nous devons supprimer non pas le MemberExpression lui-même, mais son parent, en place
hasConsoleLog = true;
nous écrivons
path.parentPath.remove();
.
De la fonction
removeConsoleLog
, nous
removeConsoleLog
toujours une valeur booléenne. Nous remplaçons sa sortie par le code qui va générer le générateur de babel, comme ceci:
hasConsoleLog
=>
babelGenerator(ast).code
Babel-generator reçoit l'arborescence de syntaxe abstraite modifiée en tant que paramètre, renvoie un objet avec la propriété de code, à l'intérieur de cet objet se trouve du code régénéré sans
console.log
. Soit dit en passant, si nous voulons obtenir une carte de code, nous pouvons appeler la propriété sourceMaps pour cet objet.
Et si vous avez besoin de trouver un débogueur?
Cette fois, nous utiliserons
ASTexplorer pour terminer la tâche. Le débogueur est un type de nœud d'instruction de débogueur. Nous n'avons pas besoin de regarder toute la structure, car il s'agit d'un type spécial de nœud, il suffit de trouver l'instruction de débogage. Nous allons écrire un plugin pour ESLint (sur ASTexplorer).
ASTexplorer est conçu de telle manière que vous écrivez le code à gauche et à droite vous obtenez le TSA fini. Vous pouvez choisir dans quel format vous souhaitez le recevoir: JSON ou sous forme d'arborescence.

Puisque nous utilisons ESLint, il fera tout le travail de recherche de fichiers pour nous et nous donnera le fichier souhaité afin que nous puissions y trouver la ligne de débogage. Cet outil utilise un analyseur ASD différent. Cependant, il existe plusieurs types de TSA en JavaScript. Quelque chose qui rappelle le passé, lorsque différents navigateurs implémentaient la spécification de différentes manières. Ainsi, nous implémentons la recherche du débogueur:
export default function(context) { return { DebuggerStatement(node) { // , console.log path, - , path context.report(node, 'LOL Debugger!!!'); // ESLint , debugger, node , , debugger } } }
Vérification du travail d'un plugin écrit:

De même, vous pouvez supprimer le débogueur du code.
Quelles sont les autres TSA utiles
Personnellement, j'utilise ASD pour simplifier le travail avec Angular et d'autres cadres frontaux. Vous pouvez importer, développer, ajouter une interface, une méthode, un décorateur et tout le reste en cliquant sur un bouton. Bien que nous parlions de Javascript dans ce cas, cependant, TypeScript a également ses propres ASD, la seule différence est la différence entre les noms des types de nœuds et la structure. Dans le même ASTExplorer peut être sélectionné comme langage TypeScript.
Donc:
- Nous avons plus de contrôle sur le code, une refactorisation plus facile, des codemods. Par exemple, avant de valider, vous pouvez appuyer sur une seule touche pour formater l'intégralité du code conformément aux directives. Codemods implique une correspondance automatique du code selon la version requise du framework.
- Moins de conflits sur la conception du code.
- Vous pouvez créer des projets de jeux. Par exemple, donnez automatiquement au programmeur des commentaires sur le code qu'il écrit.
- Meilleure compréhension de JavaScript.
Quelques liens utiles pour Babel
- Toutes les transformations Babel utilisent cette API: plugins et presets .
- Une partie du processus d'ajout de nouvelles fonctionnalités à ECMAScript consiste à créer un plugin pour Babel. Cela est nécessaire pour que les utilisateurs puissent tester la nouvelle fonctionnalité. Si vous suivez le lien , vous pouvez voir qu'à l'intérieur de la même manière les capacités de l'ASD sont utilisées. Par exemple, opérateur d'affectation logique .
- Babel Generator perd le formatage lors de la génération de code. C'est en partie bien, car si cet outil est utilisé dans l'équipe de développement, après avoir généré le code à partir de l'ASD, il aura la même apparence pour tout le monde. Mais si vous souhaitez conserver votre mise en forme, vous pouvez utiliser l'un de ces outils: Refonte ou Babel CodeMod .
- À partir de ce lien, vous pouvez trouver une mine d'informations sur Babel Awesome Babel .
- Babel est un projet open source et une équipe de bénévoles y travaille. Tu peux aider. Il y a trois façons de le faire: une aide financière, vous pouvez soutenir le site Web de Patreon, sur lequel travaille Henry Zhu, l'un des principaux contributeurs de Babel , aider avec le code sur opencollective.com/babel .
Bonus
Sinon, comment trouver notre
console.log
dans le code? Utilisez votre IDE! Utilisation de l'outil Rechercher et remplacer, après avoir sélectionné où rechercher le code.
Intellij IDEA dispose également d'un outil de «recherche structurelle» qui peut vous aider à trouver les bons endroits dans votre code, en passant, il utilise un ASD.
Du 24 au 25 novembre, Kirill fera une présentation sur les données binaires JavaScript * LOVES * à Moscou HolyJS : nous allons descendre au niveau des données binaires, creuser dans des fichiers binaires en utilisant des fichiers * .gif à titre d'exemple, et traiter des cadres de sérialisation tels que Protobuf ou Thrift. Après le rapport, il sera possible de parler avec Cyril et de discuter de toutes les questions d'intérêt dans la zone de discussion.