Aller au contenu

Lint, formatage et analyse statique : faire relire votre code par une machine

Point de théorie · à brancher dans votre pipeline CI dès que les tests y tournent

Slides du cours

Dans le chapitre CI/CD, vous avez appris à faire vérifier votre code par des tests. Ce chapitre présente une autre famille de vérifications, complémentaire et presque gratuite : des outils qui lisent votre code sans l'exécuter et y trouvent des erreurs, des incohérences et des fautes de style, en quelques secondes, sur l'ensemble du projet. Y compris sur les parties que vous n'avez pas testées.

À la fin de ce chapitre, vous serez capables de :

  1. distinguer formatage, lint et analyse statique, et dire quel type d'erreur chacun attrape ;
  2. expliquer comment un analyseur statique trouve un bug sans exécuter le programme, et lire le message qu'il produit ;
  3. installer et configurer les outils de référence de votre stack (Laravel, Django ou Node/TypeScript) ;
  4. les brancher dans votre éditeur et dans votre pipeline GitHub Actions ;
  5. les adopter progressivement sur un projet existant sans vous noyer sous les erreurs.

1. Trois familles d'outils, trois questions

Deux scènes de la vie d'un binôme

Mardi. Votre binôme ouvre une PR de vingt lignes. GitHub en affiche trois cents : son éditeur a réindenté tout le fichier et remplacé les guillemets simples par des doubles. Vous passez dix minutes à chercher les vingt lignes qui comptent, vous en ratez une, vous approuvez.

Jeudi. Un utilisateur tape une URL de conversation avec un identifiant qui n'existe pas. Erreur 500. Le code qui plante n'était pas testé, personne n'a pensé au cas. Il aurait suffi qu'un outil signale, au moment où la ligne a été écrite, que la variable pouvait être null.

Ces deux problèmes ont la même solution : des outils qui lisent le code à votre place. Et vous en utilisez déjà un sans le savoir : le soulignement rouge dans VS Code, quand vous appelez une méthode qui n'existe pas, c'est de l'analyse statique. Ce chapitre explique ce qui se passe derrière et comment l'étendre à tout le projet et à toute l'équipe.

Trois questions, trois outils

Quand on dit « le lint », on mélange souvent trois choses qui n'ont pas le même rôle. Les séparer permet de comprendre ce que chaque outil vous apporte et pourquoi on les cumule.

Trois familles d'outils : formatage, lint, analyse statique

Famille La question qu'elle pose Exemple de ce qu'elle attrape Ce qu'elle fait
Formatage « Ce code est-il écrit de manière uniforme ? » Indentation à 2 ou 4 espaces, guillemets simples ou doubles, virgule finale, longueur de ligne Réécrit le code automatiquement, sans changer son comportement
Lint « Y a-t-il des motifs suspects ou interdits ? » Variable jamais utilisée, import inutile, == au lieu de ===, console.log oublié, fonction trop longue Signale (et corrige parfois) des règles que l'équipe a choisies
Analyse statique « Ce code est-il cohérent ? Les types collent-ils ? » Méthode appelée sur un objet qui peut être null, argument de type texte passé à une fonction qui attend un entier, propriété qui n'existe pas Prouve une erreur avant toute exécution, en raisonnant sur les types

Un exemple concret dans votre chat. Le contrôleur qui affiche une conversation :

public function show(int $id)
{
    $conversation = Conversation::find($id);   // peut renvoyer null

    return view('chat.show', ['title' => $conversation->name]);
}
  • Le formateur ne dira rien : ce code est proprement écrit.
  • Le linter ne dira rien non plus : aucun motif suspect.
  • L'analyseur statique dira : « Cannot access property $name on Conversation|null ». Il a lu la signature de find(), a vu qu'elle peut renvoyer null, et a constaté que vous ne vérifiez pas ce cas. C'est exactement l'erreur 500 de jeudi.

Le même bug existe dans chaque stack, et chaque analyseur le formule à sa manière :

Stack Le code qui plante Ce que dit l'analyseur
Laravel (PHPStan) Conversation::find($id)->name Cannot access property $name on Conversation|null
Django (mypy) Conversation.objects.filter(pk=id).first().name Item "None" of "Conversation | None" has no attribute "name"
TypeScript (tsc) conversations.find((c) => c.id === id).name Object is possibly 'undefined'

Trois formulations, une seule idée : cette valeur peut être vide, et vous faites comme si elle ne l'était jamais. La correction est toujours la même : traiter le cas vide (findOrFail, get_object_or_404, un if), et l'outil se tait.

Note

Les trois familles se recouvrent un peu : ruff fait du lint et du formatage, ESLint a des règles de style, PHPStan a des règles de lint. Ce qui compte, c'est de couvrir les trois questions, pas de savoir dans quelle case ranger chaque outil.

2. Pourquoi c'est important en équipe

Le formateur met fin aux débats inutiles

Sans formateur, chaque membre du groupe code avec les réglages de son éditeur. Résultat : une PR qui touche trois lignes en affiche trois cents modifiées parce que l'éditeur de l'un a réindenté le fichier de l'autre. La review devient impossible, on ne voit plus le changement réel.

Avec un formateur, la question du style disparaît. Vous ne discutez plus de l'emplacement des accolades : l'outil décide, tout le monde applique, les diffs ne montrent que ce qui change vraiment. C'est la première chose à installer sur un projet en groupe, avant même le premier commit.

Le linter capture l'expérience des autres

Chaque règle de lint est un bug que quelqu'un a déjà eu. no-unused-vars existe parce que des milliers de développeurs ont laissé traîner une variable qu'ils croyaient utiliser. eqeqeq existe parce que '0' == false vaut true en JavaScript. Activer un jeu de règles recommandé, c'est bénéficier de cette expérience sans avoir à payer pour l'acquérir.

L'analyse statique teste ce que vous n'avez pas testé

Vos tests couvrent les chemins que vous avez pensé à écrire. L'analyseur statique parcourt tout le code, y compris les branches d'erreur, les cas limites et les fichiers que personne n'a ouverts depuis trois semaines. Il ne remplace pas les tests : il ne sait pas si votre logique métier est correcte. Mais il garantit qu'elle est cohérente, et cette garantie vaut sur cent pour cent du projet.

Astuce

Les trois outils font gagner du temps à la review. Quand la machine a déjà vérifié le style, les motifs suspects et la cohérence des types, votre binôme peut consacrer sa relecture à ce qu'aucun outil ne voit : est-ce la bonne solution, est-ce lisible, est-ce ce que l'utilisateur voulait ?

3. Comment un analyseur statique trouve un bug sans exécuter le code

Du code source à l'erreur : analyse syntaxique, inférence de types, vérification

Le principe tient en trois étapes.

  1. Il lit le code comme le fait le langage lui-même, et construit un arbre qui représente sa structure : ici une fonction, là un appel de méthode, là une condition. C'est l'arbre syntaxique, celui que l'interpréteur PHP ou Python construit avant d'exécuter quoi que ce soit.
  2. Il déduit le type de chaque expression. Conversation::find($id) : il ouvre la définition de find() dans le framework, lit son type de retour (?Conversation, c'est-à-dire Conversation ou null), et retient que $conversation peut être null. Il propage ainsi les types dans tout le programme, en suivant les affectations, les conditions et les retours de fonction.
  3. Il vérifie que chaque usage est compatible avec le type déduit. Accéder à ->name sur quelque chose qui peut être null : incompatible, il le signale. Passer une chaîne à une fonction qui attend un entier : incompatible. Appeler une méthode qui n'existe pas dans la classe : incompatible.

Tout cela sans lancer le programme, sans base de données, sans navigateur. C'est pourquoi c'est rapide (quelques secondes sur un projet étudiant) et pourquoi ça couvre tout le code.

C'est aussi exactement ce que fait votre éditeur en continu : l'autocomplétion qui vous propose ->name et pas ->nam, le soulignement rouge sur une méthode inconnue, le survol qui affiche un type. VS Code embarque un analyseur statique par langage (Intelephense pour PHP, Pylance pour Python, le serveur TypeScript pour JS/TS). Les outils de ce chapitre sont les mêmes moteurs, ou leurs cousins, lancés sur tout le projet en ligne de commande, avec un verdict vert ou rouge que la CI peut lire.

Lire un message d'outil

Tous ces outils parlent la même langue : un fichier, une ligne, un code de règle, un message. Trois exemples réels :

chat/views.py:3:8: F401 [*] `json` imported but unused
src/app.js  14:9  error  'user' is assigned a value but never used  no-unused-vars
app/Http/Controllers/MessageController.php:21 Cannot access property $name on Conversation|null.
  • Le code de règle (F401, no-unused-vars) est une clé de recherche : collez-le dans la doc de l'outil et vous obtenez la page qui explique la règle, pourquoi elle existe et comment la satisfaire. Prenez l'habitude de lire cette page avant de désactiver quoi que ce soit.
  • Le [*] de ruff, ou le mot fixable d'ESLint, signale une correction automatique disponible avec --fix.
  • PHPStan n'affiche pas de code par défaut mais son message est une phrase complète ; l'option --error-format=table le rend plus lisible en local.

Un message que vous ne comprenez pas n'est jamais une raison de désactiver la règle : c'est une raison d'ouvrir sa doc, ou de demander à votre binôme.

Le rôle des annotations de types

Plus vous donnez d'information de types, plus l'analyseur voit loin. function total($items) ne lui dit rien ; function total(array $items): float lui permet de vérifier chaque appel. En PHP et en Python, les types sont optionnels : l'analyseur fait ce qu'il peut avec ce qu'il a, et vous encourage à en ajouter. En TypeScript, les types sont le cœur du langage : le compilateur tsc est l'analyseur statique.

Les niveaux de sévérité

Un analyseur qu'on lance pour la première fois sur un projet existant remonte des centaines d'erreurs. Pour ne pas décourager, les outils proposent des niveaux :

  • PHPStan a des niveaux de 0 à 10. Le niveau 0 vérifie seulement que les classes et méthodes appelées existent ; le niveau 5 vérifie les types des arguments ; le niveau 9 traque le moindre mixed. On commence bas et on monte d'un cran quand le projet est propre.
  • mypy distingue le mode par défaut, indulgent avec le code non annoté, et --strict, qui exige des types partout.
  • TypeScript a strict: true dans tsconfig.json, qui active tout le jeu de vérifications strictes, dont strictNullChecks, le plus utile.

Les faux positifs existent : l'outil signale parfois un cas que vous savez impossible. Chaque outil permet de l'ignorer localement avec un commentaire explicite (// @phpstan-ignore-next-line, # type: ignore, // @ts-expect-error). La règle d'équipe : on ignore une erreur avec un commentaire qui dit pourquoi, jamais en silence, jamais en baissant le niveau global.

4. Les outils de référence par stack

Pour chaque stack, trois outils, une installation, une configuration minimale et la commande que vous lancerez en local et en CI. Le repo https://github.com/opmvpc/cicd26 les intègre.

Stack Formatage Lint Analyse statique
Laravel Pint Pint (règles du preset) et PHPStan PHPStan avec Larastan
Django ruff format ruff check mypy avec django-stubs, ou pyright
Node / TypeScript / Vue Prettier ESLint (avec typescript-eslint et eslint-plugin-vue) TypeScript, tsc --noEmit ou vue-tsc

Laravel : Pint et PHPStan (Larastan)

Pint est le formateur officiel de Laravel, déjà présent dans tout nouveau projet. C'est un PHP-CS-Fixer préconfiguré : il applique le style de code du framework et, au passage, une partie de ce qu'un linter ferait (imports inutilisés supprimés, ordre des imports, syntaxe modernisée). C'est pourquoi il apparaît dans deux colonnes du tableau : en PHP, formatage et lint sont assurés par le même outil. Sa configuration tient dans pint.json à la racine, et la version par défaut suffit largement :

{
    "preset": "laravel"
}
vendor/bin/pint            # reformate tout le projet
vendor/bin/pint --test     # vérifie sans modifier : la commande de la CI
vendor/bin/pint --dirty    # ne touche qu'aux fichiers modifiés depuis le dernier commit

PHPStan est l'analyseur statique de référence en PHP. Larastan est son extension pour Laravel : elle lui apprend les facades, Eloquent, les relations, les helpers, tout ce qui est magique dans le framework et que PHPStan seul ne comprendrait pas.

composer require --dev larastan/larastan

Fichier phpstan.neon à la racine :

includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    level: 5
    paths:
        - app
        - routes
vendor/bin/phpstan analyse --memory-limit=1G

Le format .neon est un cousin du YAML propre à PHPStan. --memory-limit=1G lève la limite mémoire de PHP en ligne de commande, souvent fixée à 128 Mo : PHPStan charge tout le framework en mémoire pour le lire, et s'arrête net sans cette option.

On analyse le code de l'application, pas les tests : dans les tests Pest, $this est étendu dynamiquement et PHPStan ne le comprend pas sans plugin supplémentaire. Commencez par app et routes, c'est là que vivent les bugs qui atteignent la production.

Sur le contrôleur de l'exemple, PHPStan niveau 5 remonte l'accès à ->name sur un objet potentiellement null. La correction est celle que le framework propose depuis toujours : Conversation::findOrFail($id), qui renvoie une 404 propre au lieu d'une 500.

Django : ruff et mypy

ruff a remplacé, à lui seul, une demi-douzaine d'outils Python historiques (flake8, isort, black, pyupgrade…). Il fait le lint et le formatage, il est extrêmement rapide, et sa configuration vit dans pyproject.toml :

[tool.ruff]
line-length = 120
exclude = ["**/migrations", ".venv"]

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "DJ"]   # erreurs, pyflakes, imports, bugbear, modernisation, Django
pip install ruff
ruff format .          # formate
ruff check .           # lint : la commande de la CI
ruff check --fix .     # corrige ce qui peut l'être automatiquement

Les règles DJ sont propres à Django : elles signalent par exemple un modèle sans __str__ ou un champ null=True sur un CharField.

mypy est l'analyseur de types de référence en Python. Comme PHPStan, il a besoin d'une extension pour comprendre la magie du framework : django-stubs.

pip install mypy django-stubs

Dans pyproject.toml :

[tool.mypy]
plugins = ["mypy_django_plugin.main"]
check_untyped_defs = true

[tool.django-stubs]
django_settings_module = "chatproject.settings"
mypy .

check_untyped_defs = true demande à mypy de vérifier aussi l'intérieur des fonctions sans annotations, au lieu de les ignorer : c'est ce qui rend l'outil utile sur un projet où vous n'avez pas encore tout typé. Pour qu'il voie vraiment loin, annotez vos fonctions : def validate_body(body: object) -> str | None:. Sans annotation, il considère tout comme Any et ne peut rien prouver. pyright est une alternative plus rapide et plus stricte, intégrée à VS Code via l'extension Pylance ; les deux se configurent de manière similaire.

Attention

Piège rencontré en préparant cicd26 : django-stubs donne des types génériques aux champs (ManyToManyField[Member, Conversation]). Si vous recopiez cette annotation dans un modèle, ajoutez from __future__ import annotations en tête du fichier, sinon Python évalue le générique à l'exécution et plante au démarrage. Les erreurs d'analyse statique se corrigent dans le code, mais on relance toujours les tests après : un outil peut vous pousser vers une syntaxe que le runtime ne connaît pas.

Node, TypeScript et Vue : Prettier, ESLint, tsc

Deux parcours selon votre projet. En JavaScript pur (une API Express sans TypeScript, comme le dossier node/ de cicd26) : Prettier et ESLint suffisent, l'analyse de types reste celle de l'éditeur. En TypeScript ou en Vue avec TypeScript : on ajoute tsc, qui devient votre analyseur statique. Personne ne vous demande de migrer vers TypeScript pour ce chapitre.

Prettier formate JavaScript, TypeScript, Vue, CSS, JSON et Markdown. Il est volontairement peu configurable : c'est le principe, on arrête de discuter. Un fichier .prettierrc de deux lignes suffit :

{ "semi": true, "singleQuote": false }
npm install -D prettier
npx prettier --write .    # formate
npx prettier --check .    # vérifie : la commande de la CI

ESLint est le linter. Depuis la version 9, il se configure dans eslint.config.js (la flat config). Pour un projet TypeScript avec Vue :

npm install -D eslint @eslint/js typescript-eslint eslint-plugin-vue eslint-config-prettier
import js from "@eslint/js";
import prettier from "eslint-config-prettier";
import vue from "eslint-plugin-vue";
import tseslint from "typescript-eslint";

export default tseslint.config(
  js.configs.recommended,
  ...tseslint.configs.recommended,
  ...vue.configs["flat/recommended"],
  prettier,                                     // désactive les règles de style qui feraient doublon avec Prettier
  { ignores: ["dist/", "node_modules/"] },
);

eslint-config-prettier est là pour une raison précise : ESLint a historiquement des règles de style (point-virgule, guillemets) qui entreraient en conflit avec Prettier. Ce paquet les éteint, chacun reste dans son rôle. Pour un projet JavaScript sans Vue ni TypeScript, retirez les lignes tseslint et vue, et remplacez tseslint.config( par un simple tableau.

npx eslint .

TypeScript est l'analyseur statique. Si votre projet est en TypeScript, le compilateur fait le travail ; on le lance sans produire de fichiers :

npx tsc --noEmit          # projet TS
npx vue-tsc --noEmit      # projet Vue : vérifie aussi les <template>

--noEmit veut dire « vérifie mais n'écris aucun fichier .js » : c'est Vite qui compile votre projet, tsc ne sert ici qu'à juger.

Et dans tsconfig.json, une seule option compte vraiment :

{ "compilerOptions": { "strict": true } }

Si votre projet est en JavaScript pur, vous pouvez quand même profiter de l'analyse statique : ajoutez // @ts-check en tête d'un fichier et VS Code vérifie les types déduits de votre code et des bibliothèques. C'est une bonne porte d'entrée vers TypeScript.

5. Où brancher ces outils

Trois endroits pour brancher les outils : éditeur, commit, CI

Le même outil s'utilise à trois moments, de plus en plus tard, de plus en plus contraignant.

Dans l'éditeur, en tapant

C'est là que les outils rendent le plus de service : l'erreur apparaît soulignée pendant que vous écrivez, avant même de sauvegarder. Installez les extensions VS Code de votre stack (Prettier, ESLint, Pylance ou ruff, PHP Intelephense ou l'extension PHPStan) et activez le formatage à la sauvegarde dans vos réglages :

{
  "editor.formatOnSave": true,
  "[javascript][typescript][vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[python]": { "editor.defaultFormatter": "charliermarsh.ruff" },
  "[php]": { "editor.defaultFormatter": "open-southeners.laravel-pint" }
}

Committez ce fichier dans .vscode/settings.json, et à côté un .vscode/extensions.json qui liste les extensions du projet : VS Code proposera de les installer à quiconque ouvre le dossier. Tout le groupe a la même expérience sans rien configurer, et un nouveau membre est opérationnel en deux clics.

{
  "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "charliermarsh.ruff", "ms-python.vscode-pylance", "bmewburn.vscode-intelephense-client", "open-southeners.laravel-pint"]
}

Gardez seulement les extensions de votre stack.

Au commit, avec un hook

Un hook de pre-commit lance le formateur et le linter sur les fichiers modifiés juste avant que le commit ne soit créé. Si ça échoue, le commit est refusé. C'est optionnel, mais ça évite de pousser du code que la CI va refuser cinq minutes plus tard.

  • Node : husky et lint-staged s'installent en deux commandes et ne lancent les outils que sur les fichiers concernés par le commit. La configuration tient dans package.json :

    "lint-staged": {
      "*.{js,ts,vue}": ["prettier --write", "eslint --fix"]
    }
    
  • Python : le framework pre-commit fait la même chose à partir d'un fichier .pre-commit-config.yaml.

  • Laravel : un hook shell qui appelle vendor/bin/pint --dirty suffit, ou le paquet husky si votre projet a déjà un package.json.

Attention

Un hook qui prend trente secondes sera contourné par toute l'équipe avec --no-verify. Ne mettez dans le hook que ce qui est rapide : formatage et lint des fichiers modifiés. L'analyse statique complète et les tests, c'est le travail de la CI.

Dans la CI, sur chaque PR

C'est le filet de sécurité final, celui que personne ne peut contourner. Dans le workflow du chapitre CI/CD, vous avez déjà un job lint qui tourne en parallèle des tests. Il suffit d'y ajouter l'analyse statique :

  lint:
    name: Style et analyse statique
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: shivammathur/setup-php@v2
        with: { php-version: "8.4", coverage: none }
      - run: composer install --no-interaction --prefer-dist --no-progress
      - run: vendor/bin/pint --test
      - run: vendor/bin/phpstan analyse --memory-limit=1G

Même schéma pour Django (ruff check ., ruff format --check ., mypy .) et pour Node (npx prettier --check ., npx eslint ., npx tsc --noEmit). Une fois ce job dans la liste des checks obligatoires de votre protection de branche, plus aucune variable inutilisée ni aucun null non géré ne passe sur main.

Avec un assistant IA

Si vous codez avec un agent, ces outils sont ses meilleurs alliés. Un agent qui a accès à la sortie de PHPStan ou de tsc corrige ses propres erreurs de types avant que vous ne les voyiez. Demandez-lui explicitement de lancer le lint et l'analyse statique après chaque modification, et ajoutez-le dans vos instructions de projet. C'est une des manières les plus efficaces de tenir la règle d'or du cours : comprendre et valider chaque suggestion. Ici, c'est la machine qui valide une partie du travail de la machine.

Les cousins : l'audit des dépendances

Une dernière famille d'outils lit non pas votre code, mais la liste de vos dépendances, et la compare aux bases de vulnérabilités connues. Une faille dans une bibliothèque que vous utilisez est une faille dans votre application, même si votre code est parfait.

Stack Commande Ce qu'elle fait
Laravel composer audit Compare composer.lock aux avis de sécurité Packagist
Django pip-audit (à installer) Compare les paquets installés à la base PyPI Advisory
Node npm audit Compare package-lock.json à la base GitHub Advisory

Ajoutez la commande de votre stack au job lint. Et activez Dependabot dans les réglages du repo (Security → Dependabot alerts et security updates) : GitHub ouvre alors une PR de mise à jour dès qu'une dépendance vulnérable est détectée, et votre CI vérifie que la mise à jour ne casse rien. C'est le même fichier dependabot.yml que celui du chapitre CI/CD, avec un package-ecosystem supplémentaire (composer, pip ou npm).

6. Adopter ces outils sur un projet existant

Adoption progressive : formater d'un coup, monter le niveau cran par cran

Si vous lancez PHPStan niveau 8 sur un projet de trois semaines, vous obtenez quatre cents erreurs et l'envie d'abandonner. Voici la méthode.

  1. Le formateur d'abord, d'un seul coup. Lancez-le sur tout le projet, committez le résultat seul dans un commit intitulé « Formatage », mergez. À partir de là, plus aucun diff de style ne polluera vos PR.
  2. Le linter avec les règles recommandées. Corrigez ce qui est automatique (--fix), traitez le reste à la main, ça va vite. Ajoutez le job en CI.
  3. Excluez ce que vous n'avez pas écrit. Migrations générées, vendor/, node_modules/, .venv/, dossiers de build : ils n'ont rien à faire dans l'analyse. C'est le rôle des clés exclude et ignores des configurations ci-dessus. Le reste, vous l'avez écrit, vous en répondez.
  4. L'analyse statique au niveau le plus bas qui passe. PHPStan niveau 2, mypy sans strict, TypeScript sans strict. Si même ce niveau remonte trop d'erreurs, générez une baseline : un fichier qui liste les erreurs existantes et les ignore, pour que seules les nouvelles erreurs fassent échouer la CI. PHPStan le fait avec --generate-baseline ; mypy et tsc n'ont pas d'équivalent natif, on restreint alors les dossiers analysés.
  5. Montez d'un cran quand le projet est propre. Chaque niveau supplémentaire est une session d'une heure à corriger des erreurs qui, souvent, sont de vrais bugs. C'est du temps rentable.
  6. Ne baissez jamais le niveau pour faire passer la CI. Une erreur qu'on ne comprend pas est une erreur à comprendre, pas à cacher. Si c'est un vrai faux positif, ignorez cette ligne précise, avec un commentaire qui explique pourquoi.

Astuce

Sur un projet neuf comme votre Projet 1, faites tout ça avant la première fonctionnalité. Formateur, linter, analyseur au niveau 5, job CI : une heure au total, et vous ne connaîtrez jamais le problème des quatre cents erreurs.

7. À vous : TP

Sur votre projet, en binôme, 30 minutes :

  1. Formatez. Installez le formateur de votre stack, lancez-le sur tout le projet, committez « Formatage » seul. Regardez la taille du diff : c'est tout le bruit que vous n'aurez plus jamais dans vos PR.
  2. Lintez. Activez les règles recommandées, corrigez avec --fix, puis à la main. Notez la règle qui a remonté le plus d'erreurs et discutez-en : est-ce une mauvaise habitude à corriger ou une règle à désactiver pour votre projet, et pourquoi ?
  3. Analysez. Installez l'analyseur statique au niveau le plus bas. Montez cran par cran jusqu'à ce qu'il remonte au moins une erreur. Lisez-la : est-ce un vrai bug ? Dans la majorité des cas, oui.
  4. Introduisez un bug de type volontairement. Un find() sans vérification de null, une fonction appelée avec un argument du mauvais type. Lancez l'analyseur : il doit le voir. Lancez vos tests : les voient-ils ? Probablement pas. C'est toute la complémentarité.
  5. Branchez la CI. Ajoutez les commandes au job lint de votre workflow, poussez, vérifiez le vert, ajoutez le job aux checks obligatoires de main.

Dans le rapport de projet, la section « qualité du code » gagne à contenir : les outils choisis et leur configuration, le niveau d'analyse statique atteint, et un exemple de bug attrapé par l'analyseur avant d'arriver en production.

En résumé

  • Trois questions, trois familles : le formateur uniformise, le linter signale les motifs suspects, l'analyseur statique prouve des incohérences de types. On cumule les trois.
  • Sans exécuter le code : l'analyseur lit la structure, déduit les types, vérifie chaque usage. Rapide, exhaustif, complémentaire des tests.
  • Plus vous typez, plus il voit : annotez vos fonctions en PHP et en Python, activez strict en TypeScript.
  • Les outils : Pint et PHPStan/Larastan ; ruff et mypy/django-stubs ; Prettier, ESLint et tsc/vue-tsc.
  • Un message d'outil = fichier, ligne, code de règle, phrase : le code de règle mène à la doc, la doc dit pourquoi. On ne désactive rien qu'on n'a pas compris.
  • Trois moments : dans l'éditeur (le plus utile), au commit (optionnel, rapide), en CI (obligatoire, dans les checks de branche).
  • L'audit des dépendances (composer audit, pip-audit, npm audit) et Dependabot complètent le tableau : une faille dans une bibliothèque est une faille chez vous.
  • Adoption : formater d'un coup, linter avec les règles recommandées, analyser au niveau bas et monter cran par cran. Jamais baisser le niveau pour faire passer la CI.

Pour aller plus loin