Go to Hackademy website

Un code JS impeccable grâce à ESLint

Tom Panier

Posté par dans la catégorie front

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 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).
  • 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 HTML
  • eslint-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 !


L’équipe Synbioz.
Libres d’être ensemble.

Articles connexes

Résumé de la conférence Vue.js Amsterdam 2018

20/02/2018

Avec Victor et Gaëtan, nous avons assisté à la conférence Vue.js s’étant tenue à Amsterdam à la mi-février 2018. Vous trouverez dans ce billet notre retour sur les différentes interventions auxquelles nous avons pu assister et ce que nous avons r…

Des composants universels avec React et Vue.js

24/05/2018

Dans notre article traitant de rendering avancé avec Vue.js, nous avions exploré la possibilité, pour un composant écrit pour ce framework, de tirer parti de JSX, le langage de templating initialement prévu pour être utilisé avec React. Si vous êtes…

Chérie, j'ai reduce les tableaux

11/10/2018

Depuis un peu plus d’un an que j’écris sur ce blog, je vous ai surtout parlé de Vue.js, ou encore d’outils comme ESLint ; mais tous les bons artisans vous le diront, avoir le meilleur marteau du monde n’interdit pas de s’écraser les doigts ! Aujour…

Afficher plus Afficher moins

Ajouter un commentaire