- D : s’applique spécifiquement à Django
- H : s’applique au html
- J : s’applique spécifiquement à Jinja
- M : s’applique spécifiquement à Handlebars
- N : s’applique spécifiquement à Nunjucks
- T : s’applique généralement aux modèles
djLint inclut de nombreuses règles pour vérifier le style et la validité de vos modèles. Profitez pleinement du linter en le configurant pour utiliser un profil prédéfini pour la langue du modèle de votre choix.
djlint /path/to/templates --lint
# with custom extensions
djlint /path/to/templates -e html.dj --profile=django
# or to file
djlint /path/to/this.html.j2 --profile=jinja
La plupart des règles sont activées par défaut. Les règles peuvent être désactivées en ligne de commande avec l’option --ignore. Les règles peuvent être activées avec l’option --include.
Par exemple :
djlint . --lint --include=H006,H017 --ignore=H013,H015
Cela peut également se faire par l’intermédiaire de l’option Configuration fichier.
| Code | Signification | Défaut |
|---|---|---|
| D004 | (Django) Les urls statiques doivent suivre le modèle {% static path/to/file %}. |
✔️ |
| D018 | (Django) Les liens internes doivent utiliser le modèle {% url ... %}. |
✔️ |
| H005 | La balise Html doit avoir un attribut lang non vide. |
✔️ |
| H006 | La balise img doit avoir les attributs height et width. |
- |
| H007 | LA BALISE <!DOCTYPE ... > doit être présent avant la balise html. |
✔️ |
| H008 | Les attributs doivent être entre guillemets. | ✔️ |
| H009 | Les noms de balises doivent être en minuscules. | ✔️ |
| H010 | Les noms d’attributs doivent être en minuscules. | ✔️ |
| H011 | Les valeurs des attributs doivent être citées. | ✔️ |
| H012 | Il ne doit pas y avoir d’espace autour de l’attribut =. |
✔️ |
| H013 | La balise img doit avoir des attributs alt. |
✔️ |
| H014 | Plus de lignes vides que la configuration n’en garde. | ✔️ |
| H015 | Les balises “h” doivent être suivies d’un retour à la ligne. | ✔️ |
| H016 | Balise title manquante dans le html. |
✔️ |
| H017 | Les balises vides doivent être auto-fermantes (incompatible avec : H018). | - |
| H018 | Les balises vides sont auto-fermantes par nature et doivent se terminer par “>”, et non “/>” (incompatible avec : H017). | - |
| H019 | Remplacez javascript:abc() par l’événement on_ et l’url réelle. |
✔️ |
| H020 | Couple de balises vide trouvé. Envisagez de le supprimer. | ✔️ |
| H021 | Les styles en ligne doivent être évités. | ✔️ |
| H022 | Utilisez HTTPS pour les liens externes. | ✔️ |
| H023 | N’utilisez pas de références d’entités. | ✔️ |
| H024 | Omettre le type sur les scripts et les styles. | ✔️ |
| H025 | La balise semble être orpheline. | ✔️ |
| H026 | Les balises id et class vides peuvent être supprimées. | ✔️ |
| H029 | Pensez à utiliser des valeurs de méthode de formulaire en minuscules. | ✔️ |
| H030 | Pensez à ajouter une méta-description. | ✔️ |
| H033 | Espace supplémentaire dans l’action du formulaire. | ✔️ |
| J004 | (Jinja) Les urls statiques doivent suivre le modèle { url_for('static'..) }}. |
✔️ |
| J018 | (Jinja) Les liens internes doivent utiliser le modèle {% url ... %}. |
✔️ |
| T001 | Les variables doivent être entourées d’un espace. Ex : {{ this }} |
✔️ |
| T002 | Les doubles quotes doivent être utilisées dans les balises. Ex : {% extends "this.html" %} |
✔️ |
| T003 | Le bloc de fin doit avoir un nom. Ex : {% endblock body %}. |
- |
| T027 | Chaîne non fermée trouvée dans la syntaxe du modèle. | ✔️ |
| T028 | Envisagez d’utiliser des balises sans espace à l’intérieur des valeurs d’attributs. {%- if/for -%} |
- |
| T032 | Espace blanc supplémentaire trouvé dans les balises du modèle. | ✔️ |
| T034 | Aviez-vous l’intention d’utiliser {% … %} au lieu de {% … }% ? | ✔️ |
| H036 | Évitez d’utiliser les balises br. |
✔️ |
| H037 | Attribut en double trouvé. | ✔️ |
| T038 | La balise de bloc n’a pas de balise de fin correspondante. | ✔️ |
| T039 | Balise de template non fermée trouvée. | ✔️ |
| T040 | Nom de template manquant ou vide dans une balise extends ou include. | ✔️ |
| H041 | La balise est fermée dans un bloc de template différent de celui où elle a été ouverte. | ✔️ |
| H042 | L’attribut for d’un label n’a pas d’id correspondant dans ce fichier. | ✔️ |
| H043 | La balise button devrait avoir un attribut type. |
✔️ |
| H044 | Un thead ne devrait pas mélanger des cellules th et td. |
✔️ |
| H045 | La balise iframe devrait avoir un attribut title. |
✔️ |
| H046 | La valeur de tabindex ne devrait pas être positive. | ✔️ |
| H047 | aria-hidden ne devrait pas être posé sur un élément focusable. | ✔️ |
| H048 | Cet attribut aria n’est pas défini par la spécification. | ✔️ |
| H049 | Le viewport ne devrait pas empêcher le zoom de la page. | ✔️ |
| H050 | Élément obsolète, à remplacer. | ✔️ |
| H051 | Le rôle n’est pas un de ceux qu’ARIA définit pour le balisage. | ✔️ |
| H052 | Meta refresh ne doit pas recharger ni rediriger la page après un délai. | ✔️ |
La première lettre d’un code suit le modèle :
Les variables doivent être entourées d'un espace. Ex : {{ this }}
Une syntaxe de modèle comme {{user.name}} sans espaces intérieurs est plus difficile à parcourir et à comparer dans les diffs, et un espacement incohérent à travers une base de code rend les refactorisations à base de grep (recherche d’une variable ou d’une balise) peu fiables, car la même expression existe sous plusieurs orthographes. Les guides de style de Django comme de Jinja écrivent {{ var }} et {% tag %} avec des espaces simples.
Non appliquée aux profils handlebars et golang.
À éviter :
{{user.name}}
À faire :
{{ user.name }}
Les doubles quotes doivent être utilisées dans les balises. Ex : {% extends "this.html" %}
Mélanger guillemets simples et doubles dans les balises de modèle ({% extends %}, {% include %}, {% with %}, {% trans %}, {% now %}) fait apparaître le même nom de modèle sous deux orthographes : les recherches et les renommages en masse manquent alors la moitié des occurrences. Standardiser sur les guillemets doubles garde les arguments des balises cohérents avec les guillemets des attributs HTML dans le reste du fichier.
Les guillemets simples à l’intérieur des valeurs d’attributs HTML (par exemple <span title="{% trans 'x' %}">) ne sont pas signalés, puisque les guillemets doubles de l’attribut y imposent des guillemets simples.
Avec quote_style = "single", la règle s’inverse et réclame des guillemets simples, que le formateur écrit également.
--reformat réécrit ces guillemets pour vous : un signalement ne demande jamais de travail manuel.
À éviter :
{% extends 'base.html' %}
À faire :
{% extends "base.html" %}
Le bloc de fin doit avoir un nom. Ex : {% endblock body %}.
Lorsqu’un {% block %} s’étend sur de nombreuses lignes ou que des blocs sont imbriqués, un simple {% endblock %} ne donne aucun indice sur le bloc qu’il ferme : il est alors facile de fermer le mauvais bloc en éditant ; les modèles enfants remplacent alors le mauvais contenu. Nommer le endblock documente l’appariement et permet à djLint comme à Django (qui lève une TemplateSyntaxError en cas de nom de endblock non concordant) de détecter un bloc fermé au mauvais endroit. Les erreurs d’appariement (blocs non fermés, endblock orphelins et noms non concordants) sont des vérifications de justesse assurées par T038.
Désactivée par défaut ; à activer avec --include=T003. --name-endblocks écrit le nom pour vous : un signalement ne demande jamais de travail manuel.
Un nom n’est pas requis lorsque le bloc s’ouvre et se ferme sur la même ligne, par exemple {% block title %}``{% endblock %}.
À éviter :
{% block content %}
<p>hello</p>
{% endblock %}
À faire :
{% block content %}
<p>hello</p>
{% endblock content %}
(Django) Les urls statiques doivent suivre le modèle {% static path/to/file %}.
Coder en dur les chemins /static/ contourne la balise {% static %} de Django : les modèles cassent dès que STATIC_URL change (par exemple lors du déplacement des ressources vers un CDN ou d’un déploiement sous un sous-chemin) et ne récupèrent jamais les noms de fichiers hachés de ManifestStaticFilesStorage, ce qui provoque des erreurs 404 ou des ressources obsolètes en cache en production. La règle cherche le préfixe littéral /static/ : un projet qui sert ses fichiers statiques depuis un autre chemin n’est pas couvert.
À éviter :
<link rel="stylesheet" href="/static/css/style.css">
À faire :
<link rel="stylesheet" href="{% static 'css/style.css' %}">
(Jinja) Les urls statiques doivent suivre le modèle { url_for('static'..) }}.
Coder en dur les chemins /static/ contourne url_for(‘static’, …) de Flask/Jinja : les ressources renvoient des 404 lorsque l’application est montée sous un préfixe d’URL ou que le dossier ou l’hôte statique change, et les chaînes de requête anti-cache ajoutées par le framework sont perdues. La règle cherche le préfixe littéral /static/ : un projet qui sert ses fichiers statiques depuis un autre chemin n’est pas couvert.
À éviter :
<link rel="stylesheet" href="/static/css/style.css">
À faire :
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
La balise Html doit avoir un attribut lang non vide.
Sans attribut lang sur <html>, les lecteurs d’écran devinent les règles de prononciation et peuvent lire la page dans la mauvaise langue, et les navigateurs ne peuvent pas proposer correctement la traduction, la césure ou les guillemets adaptés à la locale. Déclarer la langue de la page correspond au critère de succès 3.1.1 de WCAG 2.1 (niveau A).
lang="" et un lang sans valeur signifient tous deux que la langue est inconnue : ils sont signalés comme un attribut manquant.
À éviter :
<!DOCTYPE html>
<html>
</html>
À faire :
<!DOCTYPE html>
<html lang="en">
</html>
La balise img doit avoir les attributs height et width.
Désactivée par défaut ; à activer avec --include=H006.
Lorsqu’une <img> n’a ni width ni height, le navigateur ne peut pas réserver l’espace avant le téléchargement de l’image, donc le contenu environnant saute pendant le chargement des images. Ce décalage de mise en page dégrade le Cumulative Layout Shift (une métrique Core Web Vitals) et peut amener les utilisateurs à cliquer au mauvais endroit pendant que la page se stabilise.
À éviter :
<img src="cat.png" alt="Cat">
À faire :
<img src="cat.png" alt="Cat" width="120" height="80">
LA BALISE <!DOCTYPE ... > doit être présent avant la balise html.
Sans <!DOCTYPE> avant la balise <html>, les navigateurs rendent la page en mode quirks, en imitant le modèle de boîte et les comportements de mise en page hérités, si bien que le CSS se rend de manière incohérente d’un navigateur à l’autre. Les balises de modèle et les commentaires avant le doctype ne posent pas de problème ; seule la balise <html> elle-même doit en être précédée.
À éviter :
<html lang="en">
</html>
À faire :
<!DOCTYPE html>
<html lang="en">
</html>
Les attributs doivent être entre guillemets.
Des styles de guillemets mélangés rendent les valeurs d’attributs plus difficiles à parcourir et à rechercher, et les valeurs entre guillemets simples cassent dès que le contenu contient une apostrophe. Les guillemets doubles sont la convention utilisée par les spécifications HTML, les formateurs et la plupart des guides de style : les adopter garde les modèles cohérents avec l’écosystème.
À éviter :
<div class='content'></div>
À faire :
<div class="content"></div>
Les noms de balises doivent être en minuscules.
Les analyseurs HTML acceptent les noms de balises en majuscules, mais les sérialisations XHTML et XML sont sensibles à la casse et les rejettent, et une casse mélangée rend la recherche de texte et la relecture des diffs peu fiables (un grep sur <h1> manque <H1>). Des noms de balises en minuscules gardent les modèles portables et cohérents.
À éviter :
<H1>Welcome</H1>
À faire :
<h1>Welcome</h1>
Les noms d'attributs doivent être en minuscules.
Les noms d’attributs en majuscules sont invalides dans les sérialisations XHTML/XML et mettent en échec la recherche de texte dans les modèles (un grep sur src= manque SRC=). Le DOM normalise de toute façon les noms d’attributs HTML en minuscules, donc les orthographes en majuscules ajoutent de l’incohérence sans aucun bénéfice.
À éviter :
<img SRC="cat.png" alt="Cat" width="120" height="80">
À faire :
<img src="cat.png" alt="Cat" width="120" height="80">
Les valeurs des attributs doivent être citées.
Les valeurs d’attributs sans guillemets s’arrêtent au premier espace : une valeur comme class=btn primary perd silencieusement tout ce qui suit l’espace (le navigateur traite “primary” comme un attribut booléen séparé). Les valeurs issues de variables de modèle sont particulièrement fragiles : tout espace, “=” ou “>” rendu corrompt la balise. Les guillemets rendent la limite de la valeur explicite et sûre.
À éviter :
<div class=test></div>
À faire :
<div class="test"></div>
Il ne doit pas y avoir d'espace autour de l'attribut =.
Avec des espaces autour de “=”, la balise se lit comme trois éléments séparés, et elle est à une modification près de se disloquer : un retour à la ligne ou une troncature au milieu laisse un attribut booléen nu plus du texte parasite. Garder name=“value” d’un seul tenant est aussi ce que supposent les outils texte simples (grep, rechercher-remplacer) : un espacement hétérogène rend les attributs difficiles à trouver et à refactoriser de manière fiable.
À éviter :
<div class = "test"></div>
À faire :
<div class="test"></div>
La balise img doit avoir des attributs alt.
Sans attribut alt, les lecteurs d’écran annoncent le nom de fichier de l’image, ou rien du tout, ce qui enfreint WCAG 1.1.1 (Contenu non textuel). Le texte alternatif est aussi ce que voient les utilisateurs lorsque l’image ne se charge pas. Les images décoratives doivent porter un alt=“” explicitement vide pour que les technologies d’assistance sachent les ignorer ; cela satisfait également cette règle.
Un alt sans valeur équivaut à alt="", le cas de l’image décorative : il est accepté.
À éviter :
<img src="cat.jpg" height="200" width="300">
À faire :
<img src="cat.jpg" height="200" width="300" alt="A sleeping cat">
Plus de 2 lignes vides.
Les suites de lignes vides n’ont aucun effet sur la page rendue (le HTML replie les espaces) mais alourdissent les modèles et créent des diffs bruyants quand les lignes voisines changent. Le formateur de djLint les supprime entièrement par défaut (en conservant au plus max_blank_lines lignes vides, valeur par défaut 0), donc des suites restantes indiquent du code non formaté.
À éviter :
<div>one</div>
<p>two</p>
À faire :
<div>one</div>
<p>two</p>
Les balises "h" doivent être suivies d'un retour à la ligne.
Les titres sont des repères de niveau bloc qui définissent le plan du document ; entasser l’élément suivant sur la même ligne que la balise h fermante masque cette structure dans le source et fait apparaître toute modification de l’un des deux éléments comme un changement des deux dans les diffs. Un saut de ligne après chaque titre garde la structure visuelle du modèle alignée sur le plan rendu.
À éviter :
<h1>Heading</h1><p>Intro text.</p>
À faire :
<h1>Heading</h1>
<p>Intro text.</p>
Balise title manquante dans le html.
La spécification HTML exige un élément title dans chaque document. Sans lui, les onglets du navigateur, les favoris et l’historique affichent une URL brute au lieu d’un nom de page, les moteurs de recherche perdent l’étiquette principale de la page, et les utilisateurs de lecteurs d’écran perdent la première chose annoncée au chargement, un manquement à WCAG 2.4.2 (Titre de page, niveau A).
Ne se déclenche que sur les fichiers contenant un document <html>…</html> complet, donc les partiels et les modèles enfants qui étendent une base ne sont jamais signalés. Les coquilles de SPA qui définissent le titre côté client ont quand même besoin d’un <title> statique : c’est ce qui apparaît au premier rendu, pour les robots d’indexation et lorsque JavaScript échoue.
À éviter :
<html lang="en">
<body>Content</body>
</html>
À faire :
<html lang="en">
<head>
<title>My page</title>
</head>
<body>Content</body>
</html>
Les balises vides doivent être auto-fermantes (incompatible avec : H018).
Les modèles qui doivent aussi être analysés comme du XML/XHTML (ou alimenter des outils basés sur XML) rejettent les éléments vides écrits sans barre oblique de fermeture, et mélanger <br> et <br /> à travers une base de code produit des diffs incohérents. Cette règle impose la convention de style XHTML afin que chaque élément vide soit fermé de la même manière.
Désactivée par défaut ; à activer avec --include=H017. Mutuellement exclusive avec H018 : n’activez qu’une seule des deux conventions.
À éviter :
<br>
<meta charset="utf-8">
À faire :
<br />
<meta charset="utf-8" />
(Django) Les liens internes doivent utiliser le modèle {% url ... %}.
Les URLs internes codées en dur deviennent silencieusement obsolètes lorsque le chemin d’une route change dans urls.py, produisant des liens cassés et des actions de formulaire mortes qu’aucun test sur l’URLconf ne détectera. {% url %} résout le chemin à partir du nom de la route, donc renommer un chemin met à jour tous les liens d’un coup.
À éviter :
<a href="/accounts/login">Login</a>
À faire :
<a href="{% url 'login' %}">Login</a>
Les balises vides sont auto-fermantes par nature et doivent se terminer par ">", et non "/>" (incompatible avec : H017).
Dans le standard vivant HTML, la barre oblique finale d’un élément vide n’a aucune signification (l’analyseur l’ignore), donc écrire <br /> suggère un comportement d’auto-fermeture à la XML que le HTML n’a pas, et peut inciter les lecteurs à ajouter des barres obliques à des balises non vides, où un / parasite est silencieusement ignoré et masque des bugs de balises non fermées. Cette règle impose des fins simples en > sur les éléments vides.
Désactivée par défaut ; à activer avec --include=H018. Mutuellement exclusive avec H017 : n’activez qu’une seule des deux conventions. Le <path /> SVG est exempté, car SVG est du XML et exige la barre oblique.
À éviter :
<br />
<meta charset="utf-8" />
À faire :
<br>
<meta charset="utf-8">
(Jinja) Les liens internes doivent utiliser le modèle {% url ... %}.
Les URLs internes codées en dur cassent silencieusement lorsque le chemin d’une route change ou que l’application est montée sous un préfixe, laissant des liens morts et des actions de formulaire qui envoient vers des 404. url_for() construit l’URL à partir du nom de l’endpoint, donc les changements de routes se propagent automatiquement à tous les modèles.
À éviter :
<a href="/accounts/login">Login</a>
À faire :
<a href="{{ url_for('login') }}">Login</a>
Remplacez javascript:abc() par l'événement on_ et l'url réelle.
Les URLs javascript: cassent le clic du milieu et l’ouverture dans un nouvel onglet, ne font rien quand JavaScript est désactivé ou ne se charge pas, sont bloquées par les Content Security Policies strictes, et constituent un point d’injection XSS classique. Utilisez plutôt une véritable URL pour le href et attachez le comportement avec un gestionnaire d’événement. Sous une CSP stricte, les gestionnaires on* en ligne sont eux aussi bloqués : le onclick montré est le correctif minimal dans le modèle ; préférez attacher l’écouteur avec addEventListener depuis un fichier de script.
À éviter :
<a href="javascript:openPopup()">Open popup</a>
À faire :
<a href="{% url 'popup' %}" onclick="openPopup(event)">Open popup</a>
Couple de balises vide trouvé. Envisagez de le supprimer.
Une paire de balises vide ne rend aucun contenu mais crée quand même un nœud DOM qui peut récupérer des marges, des bordures ou des espacements flex/grid depuis les feuilles de style, produisant un espacement fantôme difficile à tracer ; c’est généralement un reste de balisage d’une modification antérieure. Les balises légitimement vides dans un balisage normal (td, th, li, dt, dd, slot) sont exemptées. Les balises portant un attribut quelconque (points de montage JS comme <div id="app">``</div>, éléments de police d’icônes comme <i class="fa fa-user">``</i>) ne sont pas signalées non plus ; seules les paires vides totalement dépourvues d’attributs sont concernées.
À éviter :
<p>Saved.</p>
<span> </span>
À faire :
<p>Saved.</p>
Les styles en ligne doivent être évités.
Les styles en ligne ont une spécificité supérieure à n’importe quel sélecteur de feuille de style, donc les surcharger ensuite exige !important ; ils sont bloqués par les Content Security Policies sans ‘unsafe-inline’ dans style-src ; et ils éparpillent la présentation dans les modèles, si bien qu’un changement de thème ou de design signifie éditer le balisage plutôt qu’une seule feuille de style. Déplacez la déclaration vers une classe CSS. Une exception légitime : les modèles d’e-mails HTML, où de nombreux clients de messagerie suppriment les blocs <style> et où les styles en ligne sont la technique standard ; excluez vos répertoires de modèles d’e-mails ou désactivez cette règle pour eux.
À éviter :
<div style="color: red;">Wrong username or password.</div>
À faire :
<div class="error">Wrong username or password.</div>
Utilisez HTTPS pour les liens externes.
Les sous-ressources en simple http:// sur une page servie en HTTPS constituent du contenu mixte : les navigateurs bloquent purement et simplement les scripts, feuilles de style et iframes, et mettent automatiquement à niveau les images ou affichent un avertissement. Un lien <a> vers une page http:// n’est pas du contenu mixte, mais il envoie tout de même les visiteurs sur une connexion non chiffrée, exposée à l’interception et à la falsification. Les références à des hôtes internes qui n’ont réellement pas de TLS seront signalées aussi ; faites taire ces endroits avec un bloc {# djlint:off H022 #} plutôt qu’en désactivant la règle.
À éviter :
<a href="http://example.com">Example</a>
À faire :
<a href="https://example.com">Example</a>
N'utilisez pas de références d'entités.
Les documents HTML5 sont en UTF-8, donc le caractère littéral fonctionne partout et c’est ce que les relecteurs lisent réellement ; une faute de frappe dans une référence d’entité (par exemple &mdsah;) n’est pas détectée par le navigateur et s’affiche telle quelle comme du texte cassé. djLint autorise les entités qui portent de la syntaxe (<, >, &, ", ', ainsi que les accolades { et } avec %, # et $, qui écrits en clair formeraient un délimiteur de template) et celles qui nomment un caractère invisible, donc impossible à relire sous forme littérale : les espaces ( ,  ,  ), les liants et marques (‌, ‍, ‎, ‏) et ­, sous forme nommée, décimale ou hexadécimale.
--reformat réécrit l’entité en caractère pour vous : un signalement ne demande jamais de travail manuel. Une entité écrite à l’intérieur d’une balise de template fait partie de la balise et non de la page : ni la règle ni le formateur n’y touchent.
À éviter :
<p>Dates 1900 — 2000</p>
À faire :
<p>Dates 1900 — 2000</p>
Omettre le type sur les scripts et les styles.
text/javascript et text/css sont les valeurs par défaut de HTML5 pour <script> et <style>, donc l’attribut est un poids mort que le navigateur ignore ; la spécification WHATWG dit explicitement de l’omettre. Le supprimer évite aussi les chaînes MIME périmées qui cassent l’élément une fois copié sur des scripts de module (où type=“module” compte réellement).
À éviter :
<script type="text/javascript" src="app.js">
À faire :
<script src="app.js"></script>
La balise semble être orpheline.
Une balise sans sa balise d’ouverture ou de fermeture correspondante force la récupération d’erreur du navigateur à deviner où l’élément se termine : le balisage qui suit se fait avaler par le mauvais élément ; la mise en page, les sélecteurs CSS et les requêtes DOM de JavaScript cassent alors silencieusement, et différemment selon les navigateurs. H025 signale aussi un <ol> ou <ul> ouvert à l’intérieur d’un <p> : l’analyseur HTML ferme le paragraphe avant la liste, donc le balisage ne s’imbrique jamais comme il est écrit.
À éviter :
<div>
<p>Hello</p>
À faire :
<div>
<p>Hello</p>
</div>
Les balises id et class vides peuvent être supprimées.
Aucun sélecteur de classe ou d’id ne correspond à un attribut vide, et un id vide est du HTML invalide (la valeur de l’id ne doit pas être la chaîne vide). Un sélecteur de présence comme div[class] le sélectionne malgré tout : le retirer est donc visible pour une feuille de style écrite ainsi. Cela signale généralement un bug de modèle où une variable devait être interpolée : le supprimer ou le remplir empêche ce bug de se cacher au grand jour.
À éviter :
<div id="" class="">content</div>
À faire :
<div>content</div>
Chaîne non fermée trouvée dans la syntaxe du modèle.
Un guillemet ouvert mais jamais fermé à l’intérieur de {% ... %} ou {{ ... }} fait mal analyser la balise par le moteur de modèles : Django et Jinja lèvent une TemplateSyntaxError au rendu ou avalent silencieusement le reste des arguments de la balise comme contenu de chaîne, si bien que la page renvoie une erreur 500 ou s’affiche avec des arguments manquants.
À éviter :
{% trans "Welcome %}
À faire :
{% trans "Welcome" %}
Envisagez d'utiliser des balises sans espace à l'intérieur des valeurs d'attributs. {%- if/for -%}
Désactivée par défaut ; à activer avec --include=T028.
L’espace qu’une balise sans espace supprime est un espace qui s’affiche : ne l’appliquez que là où l’attribut n’a rien à perdre. alt="{%- if brand -%}Acme{%- endif -%} logo" s’affiche Acmelogo, et un d="M12 {%- if big -%}20{%- endif -%} 4Z" svg devient un autre tracé. D’où le caractère optionnel de cette règle.
Les balises de modèle à l’intérieur d’une valeur d’attribut émettent dans l’attribut rendu les espaces et sauts de ligne qui les entourent : un href ou un src construit avec de simples balises {% if %}/{% for %} peut donc contenir des espaces parasites et produire des URLs cassées. Les balises de contrôle des espaces de Jinja/Nunjucks ({%- ... -%}) suppriment ces espaces environnants, si bien que l’attribut se rend comme une seule valeur propre. L’attribut class est exempté, car des espaces supplémentaires entre noms de classes sont sans conséquence.
Non appliquée au profil django : les balises de modèle Django ne prennent pas en charge le contrôle des espaces {%- -%}.
À éviter :
<a href="{% if x %}/home{% endif %}"></a>
À faire :
<a href="{%- if x -%}/home{%- endif -%}"></a>
Pensez à utiliser des valeurs de méthode de formulaire en minuscules.
La spécification HTML définit les mots-clés de méthode de formulaire en minuscules (get, post) ; les navigateurs n’acceptent les variantes en majuscules que par une correspondance de repli insensible à la casse. Conserver la forme canonique en minuscules garde les modèles cohérents et faciles à rechercher, et évite les plaintes des validateurs stricts et des chaînes d’outils basées sur XHTML.
À éviter :
<form method="POST"></form>
À faire :
<form method="post"></form>
Pensez à ajouter une méta-description.
Les moteurs de recherche utilisent la meta description comme extrait affiché sous le titre de votre page dans les résultats ; sans elle, ils synthétisent un extrait à partir d’un texte arbitraire de la page, ce qui nuit au taux de clic et produit de mauvais aperçus de lien lorsque la page est partagée.
Ne se déclenche que sur les fichiers contenant un document <html>…</html> complet. L’argument de l’extrait s’applique aux pages indexées publiquement ; pour les applications derrière authentification ou en intranet, cette règle est couramment désactivée.
À éviter :
<html lang="en">
<head><title>Home</title></head>
<body>Welcome</body>
</html>
À faire :
<html lang="en">
<head>
<title>Home</title>
<meta name="description" content="A short summary of this page.">
</head>
<body>Welcome</body>
</html>
Espace blanc supplémentaire trouvé dans les balises du modèle.
Les suites d’espaces ou de tabulations entre les arguments d’une balise de modèle sont du bruit invisible : elles masquent les vraies différences dans les diffs, peuvent rendre difficile de repérer un argument manquant, et s’écartent du style à espace unique produit par le formateur de djLint, provoquant des reformatages inutiles. Les espaces à l’intérieur des chaînes entre guillemets sont préservés et ne sont pas signalés.
À éviter :
{% static 'css/style.css' %}
À faire :
{% static 'css/style.css' %}
Espace supplémentaire dans l'action du formulaire.
Les espaces en début ou en fin de la valeur action d’un formulaire deviennent partie intégrante de l’URL rendue. Les navigateurs les suppriment à l’analyse, mais les clients hors navigateur et les tests qui utilisent la valeur littérale peuvent ne pas le faire, et autour d’une balise {% url %} l’espace parasite signale presque toujours une faute de frappe produisant une URL de soumission que le routage côté serveur ne reconnaît pas.
À éviter :
<form action="{% url 'search' %} " method="get">
<button>Search</button>
</form>
À faire :
<form action="{% url 'search' %}" method="get">
<button>Search</button>
</form>
Aviez-vous l'intention d'utiliser {% ... %} au lieu de {% ... }% ?
}% est presque toujours une faute de frappe pour %}. Le moteur de modèles ne reconnaît pas }% comme délimiteur de balise, donc la balise n’est jamais analysée : le texte brut {% … }% fuit dans le HTML rendu, ou le moteur lève une erreur de syntaxe en rencontrant la balise non fermée.
À éviter :
{% include "footer.html" }%
À faire :
{% include "footer.html" %}
N'utilisez pas les balises br pour l'espacement.
La spécification html n’autorise <br> que pour un saut de ligne faisant partie du contenu lui-même, comme dans une adresse postale ou un poème, et cet usage n’est pas touché. Ce qui est signalé est l’usage présentationnel que la spécification écarte : une suite d’au moins deux sauts, qui est de l’espace vertical, et un saut collé au bord intérieur d’un élément de bloc, qui ne rend rien que sa propre marge ne rendrait. Les deux cassent le renvoi à la ligne aux largeurs étroites, et un lecteur d’écran annonce un saut forcé là où il n’y a rien à annoncer.
À éviter :
<p>Shipping is free.<br><br>Delivery takes 3 days.</p>
À faire :
<p>Shipping is free.</p>
<p>Delivery takes 3 days.</p>
Attribut en double trouvé.
Les attributs en double sont du HTML invalide, et les navigateurs ne conservent que la première occurrence et abandonnent silencieusement les suivantes : la deuxième valeur de class ou de style ne prend donc jamais effet, ce qui cache de vrais bugs. La vérification tient compte des modèles : un attribut répété dans des branches mutuellement exclusives ({% if %}/{% else %}) n’est pas signalé, puisqu’une seule copie peut être rendue.
À éviter :
<div class="card" id="profile" class="active">...</div>
À faire :
<div class="card active" id="profile">...</div>
La balise de bloc n'a pas de balise de fin correspondante.
Une balise de bloc telle que {% if %}, {% for %} ou {% macro %} sans sa balise de fin correspondante est une TemplateSyntaxError pure et simple dans Django et Jinja : la page échoue au rendu au moment de la requête, ce que cette règle détecte avant le déploiement. Elle signale aussi les balises de fin orphelines sans balise d’ouverture et les blocs incorrectement entrelacés (par exemple {% if %}``{% for %}``{% endif %}).
L’appariement {% block %}/{% endblock %} et les noms de endblock non concordants sont vérifiés par cette règle ; T003 (désactivée par défaut) exige en plus un nom sur chaque {% endblock %} multiligne. Les balises de bloc personnalisées enregistrées via custom_blocks sont également vérifiées, y compris leur forme auto-fermante / %}.
À éviter :
{% if user.is_authenticated %}
<p>Welcome back!</p>
À faire :
{% if user.is_authenticated %}
<p>Welcome back!</p>
{% endif %}
Balise de template non fermée trouvée.
Une balise de modèle ouverte avec {{ ou {% mais jamais fermée par le }} ou %} correspondant n’est pas analysée comme une balise : Django/Jinja lèvent une TemplateSyntaxError ou rendent les accolades brutes dans la page, et tout ce qui suit jusqu’au prochain délimiteur peut être silencieusement avalé. Ces fautes de frappe (une seule accolade manquante, un délimiteur non concordant) sont faciles à manquer en relecture, car le modèle peut encore se rendre partiellement.
À éviter :
<p>{{ user.name }</p>
À faire :
<p>{{ user.name }}</p>
Nom de template manquant ou vide dans une balise extends ou include.
Une balise {% extends %} ou {% include %} dont le nom de modèle est manquant, vide ou composé uniquement d’espaces n’a rien à charger : Django lève une TemplateSyntaxError lorsque le nom est totalement absent, et TemplateDoesNotExist au rendu lorsqu’il est vide, si bien que la page renvoie une erreur 500 en production alors même que le fichier de modèle semble syntaxiquement plausible.
À éviter :
{% extends "" %}
À faire :
{% extends "base.html" %}
La balise est fermée dans un bloc de template différent de celui où elle a été ouverte.
Lorsqu’une balise HTML est ouverte dans un {% block %} mais fermée dans un autre, un modèle enfant qui ne remplace qu’un seul de ces blocs hérite de la moitié de l’élément, produisant un balisage déséquilibré dans la page rendue : les navigateurs ferment ou réimbriquent alors les éléments de façon imprévisible, cassant la mise en page et les sélecteurs CSS loin du modèle réellement modifié. Garder chaque élément ouvert et fermé dans le même bloc rend chaque bloc sûr à remplacer indépendamment.
À éviter :
{% block content %}
<div class="wrapper">
{% endblock content %}
{% block footer %}
</div>
{% endblock footer %}
À faire :
{% block content %}
<div class="wrapper">
</div>
{% endblock content %}
{% block footer %}
{% endblock footer %}
L'attribut for d'un label n'a pas d'id correspondant dans ce fichier.
La vérification ne s’exécute que sur les fichiers analysables de façon fiable : si le fichier contient quoi que ce soit pouvant rendre un id invisible ici (une sortie {{ ... }} telle qu’un widget de formulaire, un {% include %} ou {% extends %}, ou une balise de modèle inconnue), la règle reste silencieuse pour ce fichier. Là où elle s’exécute, un signalement est une association réellement cassée.
À éviter :
<label for="email">Email</label>
<input id="username">
À faire :
<label for="email">Email</label>
<input id="email">
La balise button devrait avoir un attribut type.
Un <button> sans type vaut submit, si bien qu’un bouton écrit pour lancer un script soumet aussi le formulaire qui l’entoure et la page se recharge. Écrire le type l’évite.
À éviter :
<form>
<button onclick="preview()">Aperçu</button>
</form>
À faire :
<form>
<button type="button" onclick="preview()">Aperçu</button>
</form>
Un thead ne devrait pas mélanger des cellules th et td.
Chaque ligne est jugée séparément : la ligne d’explication en td que la spécification html place dans un thead à côté de la ligne d’en-têtes n’est donc pas un mélange. Un td vide en tête de ligne est la cellule d’angle d’un tableau dont la première colonne porte des en-têtes, le balisage que recommande le tutoriel d’accessibilité du W3C ; il est ignoré.
Un th et un td n’ont pas le même sens pour un lecteur d’écran et reçoivent en général un css différent : une cellule isolée dans une ligne d’en-tête se lit donc comme une donnée et s’affiche autrement que les colonnes voisines. Le mélange est du html valide, ce qui le rend difficile à repérer.
À éviter :
<thead>
<tr>
<th>Nom</th>
<td>Taille</td>
</tr>
</thead>
À faire :
<thead>
<tr>
<th>Nom</th>
<th>Taille</th>
</tr>
</thead>
La balise iframe devrait avoir un attribut title.
Un lecteur d’écran annonce une iframe par son nom accessible. Sans ce nom, il lit l’url du cadre, ou ne dit rien, et rien n’indique ce que contient la page intégrée avant d’y entrer. La WCAG classe ce point sous 4.1.2 Nom, rôle et valeur, et axe comme html-validate activent la vérification par défaut.
Le nom peut venir de title, aria-label ou aria-labelledby ; l’un des trois suffit. Un nom écrit par une balise de template compte aussi, si bien qu’un cadre titré page par page n’est pas signalé.
À éviter :
<iframe src="/report/"></iframe>
À faire :
<iframe src="/report/" title="Rapport trimestriel"></iframe>
La valeur de tabindex ne devrait pas être positive.
Un tabindex positif place l’élément en tête de l’ordre de tabulation, devant tout ce qui n’en a pas. Un seul suffit à réorganiser la page entière pour qui navigue au clavier, et l’ordre doit ensuite être tenu à la main dans chaque gabarit qui ajoute un contrôle. La WCAG traite ce point sous 2.4.3 Ordre de focus.
0 place l’élément dans l’ordre de tabulation là où le document le met, et -1 l’en retire tout en le laissant focusable par script : ni l’un ni l’autre n’est signalé, pas plus qu’une valeur écrite par une balise de template, dont le nombre n’est pas connu ici.
À éviter :
<input tabindex="1">
À faire :
<input tabindex="0">
aria-hidden ne devrait pas être posé sur un élément focusable.
aria-hidden="true" retire l’élément de l’arbre d’accessibilité mais le laisse dans l’ordre de tabulation : la navigation au clavier s’y arrête encore et rien n’est annoncé. Masquer une icône décorative est l’usage courant de l’attribut et n’est pas signalé ; seul un élément qui prend le focus de lui-même l’est.
Un élément est considéré comme focusable s’il s’agit d’un button, select, textarea, iframe ou summary, d’un a ou area avec href, d’un input non caché, d’un audio ou video avec controls, ou de tout élément portant contenteditable ou un tabindex supérieur ou égal à 0. Un contrôle disabled, ou muni de tabindex="-1", est déjà hors de l’ordre de tabulation et reste intact.
À éviter :
<button aria-hidden="true">Close</button>
À faire :
<button type="button" aria-label="Close"><span aria-hidden="true">x</span></button>
Cet attribut aria n'est pas défini par la spécification.
Un attribut aria mal orthographié ne fait rien du tout. Aucun navigateur n’avertit, aucun lecteur d’écran ne le signale, et le balisage garde l’apparence d’avoir été rendu accessible : aria-lable peut ainsi rester des années dans un gabarit pendant que le contrôle qu’il devait nommer reste sans nom.
Les noms connus de la règle sont ceux que définit ARIA. Une liaison écrite par un framework n’est pas un nom aria simple : :aria-label, v-bind:aria-label et [attr.aria-label] sont donc ignorés.
À éviter :
<button type="button" aria-lable="Close">x</button>
À faire :
<button type="button" aria-label="Close">x</button>
Le viewport ne devrait pas empêcher le zoom de la page.
user-scalable=no, ainsi qu’un maximum-scale inférieur à 2, empêchent d’agrandir la page sur un téléphone, ce qui est pour beaucoup le seul moyen de la lire. La WCAG demande 200 % sous 1.4.4 Redimensionnement du texte. Les navigateurs ignorent de plus en plus cette restriction, mais la balise coupe encore le zoom partout où elle est respectée.
Le nom et le contenu sont trouvés quel que soit l’ordre dans lequel les deux sont écrits.
À éviter :
<meta name="viewport" content="width=device-width, user-scalable=no">
À faire :
<meta name="viewport" content="width=device-width, initial-scale=1">
Élément obsolète, à remplacer.
<center>, <font>, <big>, <strike> et <tt> ont disparu lorsque css a pris en charge la présentation, et <marquee>, <blink>, <nobr> et <spacer> n’ont jamais été standard. Html n’en définit plus aucun, donc rien ne garantit la manière dont un navigateur les dispose, et une feuille de style ne peut pas les cibler comme elle cible une classe.
Les éléments que la règle connaît sont acronym, applet, basefont, bgsound, big, blink, center, dir, font, frame, frameset, isindex, keygen, marquee, menuitem, nobr, noembed, noframes, plaintext, spacer, strike, tt et xmp. Seule la balise ouvrante est signalée, de sorte que chaque élément n’est nommé qu’une fois, et un élément personnalisé dont le nom ne fait que commencer par l’un d’eux, comme <font-picker>, est laissé tel quel.
À éviter :
<center><font color="red">Warning</font></center>
À faire :
<p class="warning">Warning</p>
Le rôle n'est pas un de ceux qu'ARIA définit pour le balisage.
Un rôle qu’aucune spécification ne nomme est purement et simplement ignoré, et l’élément conserve le sens qu’il avait déjà : role="buton" laisse une <div> être une <div>, c’est-à-dire rien pour un lecteur d’écran, alors que le balisage donne l’impression d’avoir reçu une intention. Rien ne le signale, et c’est ce qui rend la faute de frappe intéressante à détecter.
Les noms que la règle connaît sont les rôles qu’ARIA définit pour les auteurs, auxquels s’ajoutent ceux de DPUB-ARIA (doc-chapter et les autres) et de GRAPHICS-ARIA (graphics-symbol et les autres). Les rôles abstraits d’ARIA, comme landmark et sectionhead, sont signalés : la spécification indique qu’ils servent à construire son ontologie et ne doivent pas être écrits dans le balisage.
Un role peut contenir plusieurs noms, sous forme de liste de repli, et chacun est vérifié. Une valeur contenant de la syntaxe de gabarit est indéterminable et est laissée telle quelle, tout comme une liaison écrite par un framework, comme :role ou [attr.role].
À éviter :
<div role="buton">Save</div>
À faire :
<button type="button">Save</button>
Meta refresh ne doit pas recharger ni rediriger la page après un délai.
Un rafraîchissement minuté déplace la page sous les yeux de qui la lit. Une personne qui lit lentement, qui utilise un lecteur d’écran ou qui a simplement été interrompue perd sa place sans avertissement et sans pouvoir l’empêcher, ce qui constitue un échec du critère WCAG 2.2.1 « Réglage du délai », et un rafraîchissement qui recharge la même page jette ce qui y avait été saisi.
Un délai de zéro est une redirection immédiate plutôt qu’un minuteur, ce que WCAG autorise là où une redirection serveur n’est pas possible : il n’est donc pas signalé. Les deux attributs sont trouvés quel que soit l’ordre dans lequel ils sont écrits.
À éviter :
<meta http-equiv="refresh" content="30">
À faire :
<meta http-equiv="refresh" content="0; url=/next-page">
Nous accueillons volontiers les pull requests contenant de nouvelles règles !
Une bonne règle consiste en
Veuillez inclure un test pour valider la règle.
Il est possible d’ajouter des règles personnalisées directement au sein de votre projet.
Pour cela, créez un fichier .djlint_rules.yaml à côté de votre pyproject.toml.
Un fichier de règles situé ailleurs peut être indiqué avec l’option CLI --rules.
Des règles peuvent être ajoutées à ce fichier et djLint les reprendra.
Vous pouvez ajouter une règle qui échouera si l’un des regex listés dans patterns
est trouvé dans le code html.
- rule:
name: T001
message: Trouver la Trichotillomanie
flags: re.DOTALL|re.I
pattern:
- Trichotillomanie
Vous pouvez ajouter une règle qui va importer et executer une fonction python
personalisée.
- rule:
name: T001
message: Le mot 'bad' a été trouvé
python_module: votre_package.votre_module
Le module indiqué dans python_module doit contenir une fonction run() qui sera
executé sur chacun des fichiers testés. La fonction doit accepter les arguments suivants :
rule: Le dictionnaire python qui représente votre règle dans .djlint_rules.yaml.name et message que vous avez défini dansyaml.config: L’objet de configuration global de DJLint.html: Le contenu html complet du fichier testé.filepath: Chemin du fichier testé.line_ends: Liste qui, pour chacune des lignes du fichier html testé, contient unstart et end qui donnent les indexes globaux dans le fichier dudjlint.lint.get_line()*args, **kwargs: Il est possible que nous ajoutions d’autres arguments à l’avenir,La fonction doit retourner une liste de dictionnaire, un pour chacune des erreurs
trouvées. Le dictionnaire doit contenir les clées suivantes :
code: Code de la règle qui rapporte l’erreur (généralement rule['name'])line: Numéro de ligne et numéro de caractère dans cette ligne, séparées par un :."2:3" veut dire que l’erreur a été trouvée sur la ligne 2, au caractère 3.match: La partie du contenu qui contient l’erreurmessage: Le message qui serra affiché pour signaler l’erreur (généralement rule['message'])from typing import Any, Dict, List
from djlint.settings import Config
from djlint.lint import get_line
import re
def run(
rule: Dict[str, Any],
config: Config,
html: str,
filepath: str,
line_ends: List[Dict[str, int]],
*args: Any,
**kwargs: Any,
) -> List[Dict[str, str]]:
"""
Rule that fails if if the html file contains 'bad'. This is just an exemple, in
reality it's much simpler to do that with "pattern rule".
"""
errors: List[Dict[str, str]] = []
for match in re.finditer(r"bad", html):
errors.append({
"code": rule["name"],
"line": get_line(match.start(), line_ends),
"match": match.group().strip()[:20],
"message": rule["message"],
})
return errors