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.
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 :
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 !
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 supporter import
et export
) 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ême npm 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"
(pour window
, etc.) et
"es6"
(pour les « nouveaux » standards tels que Promise
)."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 !
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 :
"warn"
pour un avertissement,
"error"
pour une erreur ou "off"
pour désactiver complètement la règle)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 !
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).
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 ."
}
}
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.
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 ."
}
}
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.
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 !
L’équipe Synbioz.
Libres d’être ensemble.
Nos conseils et ressources pour vos développements produit.