Article écrit par Tom Panier
Après avoir vu ensemble comment écrire des tests unitaires pour votre application JavaScript on pourrait s’imaginer que notre codebase a atteint le pinacle de la qualité. C’est sans compter sur les obsessions maniaques de votre serviteur (c’est moi) qui va aujourd’hui vous présenter son meilleur ami celui qu’il a attendu toute sa vie sans vraiment le savoir j’ai nommé ESLint !
Comme le savent ceux qui me connaissent un tant soit peu j’adore donner mon avis aussi allons-nous voir aujourd’hui comment mettre en place cet outil et comment je vous recommande de l’utiliser.
Mais quelle est donc cette chose dont tu nous rebats les oreilles ?
ESLint est comme son nom l’indique un linter c’est-à-dire un outil qui analyse statiquement du code et vérifie que celui-ci respecte un certain nombre de règles celles-ci étant bien évidemment configurables de manière (très) fine.
L’intérêt est multiple :
- vous êtes assuré·e de la constance du coding style qu’il s’agisse de bonnes pratiques ou de considérations plus esthétiques : autant de points plus ou moins triviaux dont vous n’aurez plus à vous soucier directement
- en cas d’erreur de syntaxe dans votre code l’analyse statique de ce dernier échouera et l’erreur en question vous sera remontée : c’est un garde-fou supplémentaire
ESLint est disponible sous la forme d’un paquet NPM qui vous donne le choix entre une installation globale et locale. Je privilégie personnellement la seconde solution considérant qu’un projet doit déclarer explicitement les outils dont il a besoin pour fonctionner (les devDependencies
du package.json
sont là pour ça après tout). Il est toutefois important de noter qu’une installation globale permet une intégration de l’outil à la plupart des éditeurs de code utilisés de nos jours et qu’après tout l’un n’empêche pas l’autre. Pour l’heure installons-le dans notre projet :
$ npm install eslint --save-dev
Ajoutons également un script dédié à package.json
afin d’abstraire les potentielles évolutions futures de la commande à lancer :
{ "scripts": { "lint": "eslint ." } }
Notez que l’on peut ici se contenter d’écrire eslint .
NPM incluant les exécutables disponibles dans node_modules
par défaut dans son path. Depuis la console nous aurions dû taper node_modules/eslint/bin/eslint.js .
(à moins d’utiliser npx
mais c’est une autre histoire).
Testons le script en question :
$ npm run lint
Oops! Something went wrong! :( ESLint: 5.5.0. ESLint couldn't find a configuration file. To set up a configuration file for this project please run: eslint --init ESLint looked for configuration files in /path/to/project and its ancestors. If it found none it then looked in your home directory. If you think you already have a configuration file or if you need more help please stop by the ESLint chat room: https://gitter.im/eslint/eslint
Nous n’avons en effet pas de fichier de configuration qui indiquerait à ESLint quelles règles appliquer… nous allons de ce pas y remédier !
Posons les bases
Bien qu’il soit possible de créer interactivement le fichier manquant tel que l’outil nous le suggère je vous propose de le faire « à la main » afin de bien comprendre de quoi il retourne. Créons donc à la racine de notre projet un fichier .eslintrc
avec le contenu suivant :
{ "root": true "parserOptions": { "ecmaVersion": 2018 "sourceType": "module" "parser": "babel-eslint" } "env": { "browser": true "es6": true } "extends": [ "eslint:recommended" ] }
Décomposons ensemble cette configuration de départ afin d’en saisir toutes les subtilités :
"root": true
indique à ESLint de ne pas rechercher de fichier de configuration dans des répertoires plus hauts que celui dans lequel se trouve ce fichier à savoir la racine du projet : en effet le comportement par défaut de l’outil est de chercher un tel fichier dans le répertoire courant puis de remonter de niveau en niveau afin d’agréger la configuration qui sera réellement prise en compte. Nous partons du principe que tout projet porte sa propre configuration (ou que nous indiquerons pour elle un chemin spécifique mais nous en reparlerons plus bas) et désactivons donc cette fonctionnalité par souci d’économie."parserOptions"
précise respectivement la version d’ECMAScript (ES en abrégé — vous l’avez ?) prise en compte pour l’analyse du code le « type de source » ("module"
doit être utilisé pour supporterimport
etexport
) et le parser spécifique à utiliser le cas échéant. Comme nous sommes en 2018 et que l’écosystème JavaScript est ce qu’il est il y a de fortes chances que votre projet utilise Babel pour mettre à votre disposition des syntaxes du futur je vous mâche donc le travail. Ne me remerciez pas (mais jouez quand mêmenpm install babel-eslint --save-dev
pour que ça continue à marcher)."env"
complète la section précédente en indiquant quel(s) environnement(s) d’exécution concerne(nt) votre code et notamment quelles variables globales pourront y être rencontrées. Il y a une pléthore de valeurs possibles mais dans notre cas nous spécifions"browser"
(pourwindow
etc.) et"es6"
(pour les « nouveaux » standards tels quePromise
).- Enfin de manière assez limpide
"extends"
permet de spécifier un ou plusieurs rulesets sur lesquels s’appuyer afin de démarrer avec quelque chose d’assez standard que l’on pourra ensuite ajuster à l’envi.
Ce dernier point étant une excellente transition — parfois je me laisse pantois — voyons sans plus attendre comment paramétrer les règles elles-mêmes !
Les règles sont faites pour être configurées
En l’état les règles utilisées lorsque nous lançons npm run lint
sont donc celles recommandées par les développeur·euse·s d’ESLint qui connaissent probablement plutôt bien les bails des best practices de rédaction de code. Néanmoins il y a fort à parier que vos besoins divergent de ce tronc commun ; je vais même vous proposer ici une modification assez radicale par rapport à ce standard mais qui donne un résultat que je trouve sémantiquement plus cohérent.
L’outil distingue deux niveaux de sévérité pour les violations (oui c’est comme ça qu’on dit) qu’il remonte respectivement les erreurs et les avertissements. Vous vous rappelez que j’ai dit que les erreurs de syntaxe dans le code seraient également remontées comme des erreurs par ESLint n’est-ce pas ? Hé bien précisément pour cette raison mon postulat est que toutes les violations du coding style devraient être remontées comme des avertissements afin de bien les distinguer des vraies erreurs dans le code. Après tout du code moche mais qui marche mérite bien un avertissement mais pas d’être considéré comme « en erreur »… je vous laisse vous faire votre propre opinion.
Si vous êtes d’accord avec moi la méthodologie susmentionnée a tout de même un petit inconvénient : par défaut les warnings remontés par ESLint n’influencent pas le code de retour de l’exécution de ce dernier — autant dire qu’ils sont peu utiles dans un contexte d’automatisation ou même d’un point de vue purement sémantique. Nous allons donc modifier la commande lancée par npm run lint
dans package.json
(vous voyez qu’on a bien fait d’en faire un script !) :
{ "scripts": { "lint": "eslint --max-warnings=0 ." } }
De cette façon ESLint retournera bien un code d’erreur si des avertissements sont lancés. Quoi que vous choisissiez les règles se configurent dans .eslintrc
de la manière suivante :
{ // ... "rules": { "rule-name": /* value */ } }
Étonnant non ? Plus sérieusement la valeur en question peut prendre différentes formes :
- un niveau de sévérité sous forme de chaîne (
"warn"
pour un avertissement"error"
pour une erreur ou"off"
pour désactiver complètement la règle) - un tableau de configuration dont le premier élément est le niveau de sévérité comme au-dessus et les suivants les éventuels arguments à passer à la règle ; le dernier d’entre eux est typiquement un objet de configuration plus ou moins velu selon la règle concernée
Un exemple valant mieux qu’un long discours en voici un (d’exemple pas de long discours) :
{ // ... "rules": { "eqeqeq": "warn" "indent": ["warn" 2 { "SwitchCase": 1 }] "multiline-ternary": ["warn" "always-multiline"] } }
La configuration ci-dessus remontera des avertissements si vous utilisez un opérateur d’égalité non stricte (tel que ==
) si votre code n’est pas correctement indenté avec deux espaces (avec un niveau d’indentation supplémentaire pour les case
à l’intérieur de leur switch
) ou encore si vous écrivez une condition ternaire sur plusieurs lignes mais que vos retours chariot ne se situent pas au niveau des opérateurs ?
et :
.
Bien sûr il y a beaucoup de règles (c’est un euphémisme) et nul n’est censé toutes les connaître ; je ne peux trop vous conseiller de vous référer à la documentation idoine afin de commencer à appréhender la puissance d’ESLint.
Ne le dites pas à vos développeur·euse·s mais vous pouvez également utiliser l’option --fix
pour que l’outil corrige automatiquement certaines violations — pas toutes évidemment mais en tout cas toutes celles pour lesquelles le changement concerné n’a aucune chance d’impacter l’exécution du code (tout ce qui concerne les espaces par exemple).
Maintenant que vous voilà tout·e impressionné·e… allons plus loin !
L’exception confirme la règle
Il y a fort à parier que vous rencontrerez des cas où il serait pertinent de désactiver localement une règle ou encore d’apporter une précision à ESLint afin qu’il interprète correctement un unique fichier dans votre projet : on peut prendre l’exemple des fichiers de configuration des applications Vue.js créées via le CLI qui utilisent module.exports
. Dans un tel cas il suffit d’ajouter en haut du fichier un commentaire particulier (une annotation) afin de donner du contexte à l’outil :
/* eslint-env node */ module.exports = /* ... */;
Pour ce qui est des désactivations locales de règles plusieurs types d’annotation sont disponibles :
/* eslint-disable eqeqeq indent */ // Les deux règles mentionnées ci-dessus seront ignorées // jusqu'à ce qu'ESLint rencontre l'annotation "fermante" /* eslint-enable eqeqeq indent */ // eslint-disable-next-line eqeqeq if (true == false) { // ... } else if (false == true) { // eslint-disable-line eqeqeq // ... }
Si vous ne listez aucune règle la totalité d’entre elles seront désactivées (ou réactivées) par l’annotation en question (idéalement ne faites pas ça même s’il est toujours possible d’avoir une bonne raison de le faire).
Plugins baby
Si les règles disponibles nativement sont comme je le disais nombreuses elles ne couvrent certainement pas tous les besoins que vous pouvez avoir ne serait-ce que dans le cadre de l’utilisation d’un framework spécifique avec sa syntaxe et ses bonnes pratiques particulières. C’est pourquoi ESLint embarque un système de plugins lui permettant d’être étendu à volonté avec des règles écrites par la communauté pour tel ou tel usage. En voici pêle-mêle quelques-uns que je vous recommande chaudement :
eslint-plugin-html
qui permet le parsing du code JavaScript dans les balises<script>
des fichiers HTMLeslint-plugin-import
qui fournit des règles plus fines sur la syntaxe des modules ES6+eslint-plugin-vue
qui assure le support des SFC de Vue.js (des équivalents existent évidemment pour votre framework fétiche)
Si l’utilisation de plugins implique l’analyse de fichiers portant une autre extension que .js
il vous faudra ajuster la commande à lancer en conséquence :
{ "scripts": { "lint": "eslint --ext .html .js .vue --max-warnings=0 ." } }
Fais tourner ta config
Si vous avez plusieurs projets et plusieurs développeur·euse·s garantir la cohérence du coding style à travers tout ça peut sembler relever de la partie de mah-jong avec un compte à rebours et des moufles. Il y a pourtant une solution assez simple pour peu qu’on s’en donne les moyens : mettre à disposition votre fichier de règles de façon à pouvoir l’installer en tant que dépendance NPM.
Les avantages sont nombreux : votre ruleset est installé automatiquement avec votre projet dans la bonne version (grâce à quoi vous pouvez gérer sereinement les « cassures de compatibilité ») et vos développeurs peuvent aussi l’installer en local pour en bénéficier dans leur éditeur comme je le mentionnais en début d’article.
Un registry NPM privé est pour cela la solution idéale mais un simple dépôt Git fera l’affaire à défaut ; dans les deux cas ajoutez la dépendance en question à la section devDependencies
avec le reste modifiez (encore) le script comme suit :
{ "scripts": { "lint": "eslint -c node_modules/my-awesome-ruleset/.eslintrc --max-warnings=0 ." } }
…et roulez jeunesse ! L’option -c
peut évidemment être utilisée pour tout cas où vous voudriez spécifier un fichier de configuration particulier lors de l’exécution.
La loi c’est moi
Je suis sûr que vous êtes déjà bluffé·e par les grandioses capacités de l’outil (non je n’ai rien touché pour la rédaction de cet article). Mais si je vous disais que vous pouvez écrire vos propres règles ?
Nous n’allons pas entrer dans les détails outre mesure mais sachez que c’est bien plus simple qu’il n’y paraît : une règle ESLint n’est ni plus ni moins qu’un module JavaScript exposant une ou plusieurs fonctions permettant d’interagir avec l’AST du code parcouru et de lever une erreur si par exemple tel token (un if
) est immédiatement suivi de tel autre token (une parenthèse ouvrante et non un espace). Détailler la syntaxe précise sortirait du périmètre de cet article mais comme toujours je vous conseille vivement de lire la documentation qui va bien. Je peux en tout cas affirmer par expérience qu’on prend vite le coup de main et que les possibilités sont là encore fort appréciables.
Pour ce qui est de l’exécution je vous le donne en mille il faut ajouter une option à notre script préféré en l’occurrence --rulesdir
:
{ "scripts": { "lint": "eslint --rulesdir path/to/your/rules/ --max-warnings=0 ." } }
Good boy!
Pour finir en beauté voyons comment faire en sorte de lancer automatiquement ESLint lors d’un commit et/ou d’un push afin de garantir que du code non vérifié ne finisse sur votre remote Git et ce grâce à un outil au poil brillant nommé Yorkie :
$ npm install yorkie --save-dev
{ "scripts": { "lint": "eslint --max-warnings=0 ." } "gitHooks": { "pre-push": "npm run lint" } }
C’est aussi simple que ça ! Des hooks Git seront désormais automatiquement configurés à l’installation de votre projet et la commande indiquée sera lancée au moment opportun afin de faire échouer l’opération si des violations subsistent dans le code. Si votre projet comporte des tests et que l’exécution de ces derniers est rapide npm test && npm run lint
est une commande idéale à utiliser.
[Insérer un titre de paragraphe pertinent ici]
C’est tout pour ce rapide (si si) tour d’horizon de l’outil plus que complet qu’est ESLint. Une phase de prise en main et de fine-tuning sera évidemment nécessaire pour adapter celui-ci à vos besoins mais l’adopter vous fera gagner du temps de l’énergie ou même les deux : n’attendez donc pas plus longtemps pour lui donner sa chance si ce n’est pas déjà fait !
Il ne me reste plus qu’à vous remercier pour votre lecture et à vous souhaiter une excellente fin de journée ou de nuit si vous vous êtes affranchi des normes sociales des braves gens. Ciao !