Aller au contenu principal
50 % de réduction toutes les offres, durée limitée. À partir de $2.48/mo
14 min left
Outils dev et DevOps

Comment configurer un linter pour le code généré par l'IA

S By Sherwin 14 min read
ESLint output showing AI-specific lint violations in a TypeScript file

Les agents IA savent générer du code qui compile.

Ce n'est pourtant pas le niveau d'exigence que vous visez. L'exigence, c'est un code qui n'importe pas des éléments dont il ne se sert jamais, qui ne contourne pas le système de types à coups de cast « any », qui n'ignore pas les valeurs d'erreur retournées et qui ne code pas en dur des identifiants que gosec devrait détecter avant la revue.

Les modèles d'IA s'entraînent sur de vieilles réponses de Stack Overflow et reproduisent les schémas qu'ils y ont appris, notamment des API obsolètes, des annotations de type manquantes et des fonctions techniquement correctes mais trop volumineuses pour être relues en toute sécurité. Il vous faut un linter dans la boucle. Pas comme une suggestion, mais comme une barrière.

Ce guide couvre la configuration de trois écosystèmes (Python avec Ruff, TypeScript/JavaScript avec la flat config d'ESLint v10, et Go avec golangci-lint) avec des règles ajustées spécifiquement pour les schémas d'échec qu'introduit l'IA. Il explique ensuite comment rendre la barrière bien plus difficile à contourner, pour que l'agent ne puisse pas simplement esquiver les hooks locaux avec --no-verify sans qu'une autre couche ne l'intercepte.

La configuration est étagée : le linting au niveau de l'IDE détecte les problèmes en ligne au fur et à mesure que l'agent écrit, les hooks pre-commit interceptent tout ce qui atteint une tentative de commit, et la CI intercepte tout ce qui passe en local. Chaque couche est indépendante, et vous pouvez choisir les sections de langage qui correspondent à votre stack. La couche d'application fonctionne de la même façon quel que soit le langage que vous lintez.

En bref

  • Ruff (Python), ESLint v10 (TS/JS) et golangci-lint (Go) disposent chacun de règles précises qui détectent les échecs les plus fréquents de l'IA
  • Les configurations ci-dessous sont annotées ; chaque règle est là pour une raison
  • Lefthook gère la barrière pre-commit ; le hook afterFileEdit de Cursor lance le lint en ligne
  • Quatre couches d'application rendent bien plus difficile pour les agents IA de contourner la barrière avec --no-verify
  • La CI est le dernier rempart : les agents ne peuvent pas passer --no-verify à GitHub Actions

Comment configurer Ruff pour le code Python généré par l'IA

Terminal output from Ruff flagging unused imports and a missing return type annotation in AI-generated Python code

Ruff est le bon linter Python pour les bases de code assistées par l'IA. Il est assez rapide pour s'exécuter à chaque sauvegarde de fichier sans rien bloquer, il couvre à la fois le style et les vraies erreurs de logique, et il embarque un comportement de formateur (en remplacement de Black) dans le même binaire. Les règles ci-dessous ciblent les schémas d'échec précis que les modèles d'IA introduisent le plus souvent en Python.

Installer Ruff

pip install ruff

# or via uv (faster for new projects):
uv add --dev ruff

C'est tout. Pas d'écosystème de plugins, pas de négociation de dépendances pairs.

La configuration pyproject.toml

[tool.ruff]
line-length = 88
target-version = "py311"

[tool.ruff.lint]
select = [
  "E",   # pycodestyle — style consistency
  "F",   # pyflakes — catches unused imports (the most common AI artifact)
  "I",   # isort — import ordering (AI frequently reorders imports incorrectly)
  "N",   # pep8-naming — naming conventions
  "UP",  # pyupgrade — flags deprecated APIs (AI trains on old Stack Overflow answers)
  "S",   # flake8-bandit — security rules: subprocess.shell=True, eval(), hardcoded creds
  "ANN", # type annotation enforcement (AI frequently omits return type annotations)
]
ignore = []  # intentional: do not add sweeping ignores in AI-assisted codebases

[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]  # allow assert in tests

Les règles F sont rentabilisées immédiatement. Le code IA génère des instructions d'import pour des paquets qu'il finit par ne pas utiliser, et F401 (import inutilisé) les détecte tous. Les règles UP repèrent les appels à des schémas d'API obsolètes que l'IA a appris dans des réponses Python antérieures à la 3.10 ; à elles seules, UP006 et UP007 signalent des dizaines de schémas de typage superflus. Les règles S (Bandit) détectent les failles de sécurité : chaînes codées en dur qui ressemblent à des identifiants (S105/S106), injection shell via subprocess(shell=True) (S603/S607), choix de cryptographie faible (S324).

Le ignore = [] est délibéré. Chaque exception que vous ajoutez à cette liste est une catégorie d'échec de l'IA que vous avez décidé d'autoriser.

Exécuter Ruff

ruff check .           # lint only — see what's wrong
ruff check --fix .     # auto-fix safe violations (imports, formatting)
ruff format .          # format the codebase (replaces Black)

--fix applique par défaut les corrections sûres de Ruff, comme la suppression des imports inutilisés ou l'application de corrections simples de formatage et de lint. Ruff propose aussi des corrections non sûres, mais celles-ci nécessitent une activation explicite et doivent être examinées plus attentivement. Examinez manuellement tout ce que Ruff ne peut pas corriger en toute sécurité.

Comment configurer ESLint v10 pour le code TypeScript et JavaScript généré par l'IA

ESLint v10 flat config reporting no-explicit-any and max-lines-per-function violations in an AI-generated TypeScript file

ESLint v10 a abandonné l'ancien format de configuration .eslintrc.*. Tout passe désormais par la flat config dans eslint.config.mjs. Si vous tombez sur un tutoriel qui utilise .eslintrc.json ou .eslintrc.js, il vise ESLint v8 ou v9, où la syntaxe est différente. Utilisez ce qui suit.

Les règles @typescript-eslint de cette configuration ciblent les modes d'échec précis qui reviennent de façon répétée dans le TypeScript généré par l'IA : l'échappatoire any, les fonctions monolithiques difficiles à relire, et les valeurs codées en dur qui devraient être des constantes.

Installer ESLint v10 avec la prise en charge de TypeScript

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# or with pnpm:
pnpm add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

ESLint v10.5.0 est la version actuelle en juin 2026. Les paquets @typescript-eslint doivent correspondre à votre version de TypeScript ; consultez leur README pour la matrice de compatibilité.

La configuration eslint.config.mjs

import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';

export default [
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: tsParser,
      parserOptions: { project: './tsconfig.json' },
    },
    plugins: { '@typescript-eslint': tseslint },
    rules: {
      // AI defaults to `any` to bypass the type system — this blocks it
      '@typescript-eslint/no-explicit-any': 'error',

      // AI generates variables it declares but never uses
      '@typescript-eslint/no-unused-vars': 'error',

      // AI writes functions that work but are too long to review safely
      'max-lines-per-function': ['error', { max: 50 }],

      // AI overloads function signatures; this forces decomposition
      'max-params': ['error', 2],

      // AI hardcodes values that should be named constants
      'no-magic-numbers': ['error', { ignore: [0, 1, -1] }],

      // AI writes files that are too large to reason about in a single review
      'max-lines': ['error', { max: 250 }],

      // AI leaves console.log debugging in production code
      'no-console': 'warn',
    },
  },
];

La règle max-lines-per-function: 50 est l'élément le plus agressif de cette configuration. Vous la déclencherez sans arrêt au premier passage dans une base de code assistée par l'IA. C'est précisément le but. Vous devriez la déclencher. Les fonctions qui dépassent 50 lignes sont la première chose qui devient impossible à appréhender quand vous relisez du code généré par l'IA en volume.

La règle max-params: 2 force la décomposition. Les modèles d'IA apprennent à partir de bases de code où les fonctions à cinq arguments sont normales ; la règle s'y oppose en exigeant que l'agent utilise un objet d'options, ce qui constitue une meilleure conception et se lit plus facilement.

Exécuter ESLint

npx eslint .                       # lint
npx eslint . --fix                 # auto-fix safe issues
npx eslint . --max-warnings 0      # CI mode — treats warnings as errors

Utilisez --max-warnings 0 dans votre étape de CI. Cela fait passer les avertissements no-console de « techniquement signalés » à « réellement bloquants ».

Facultatif : des règles plus strictes pour les fichiers générés par l'IA

Si votre équipe utilise une convention de nommage des fichiers pour marquer le code généré par l'IA (*.ai.ts, *-generated.ts, ou similaire), vous pouvez appliquer des règles plus serrées spécifiquement à ces fichiers :

// Add to eslint.config.mjs after the main config object
{
  files: ['**/*.ai.ts', '**/*.ai.tsx', '**/*-generated.ts'],
  rules: {
    'max-lines': ['error', { max: 100 }],  // tighter file ceiling
    'complexity': ['error', 5],             // McCabe complexity limit
  },
}

Comment configurer golangci-lint pour le code Go généré par l'IA

golangci-lint est le lanceur multi-linter standard pour Go. Il embarque gosec, errcheck, staticcheck et plus de 40 autres linters configurables depuis un seul fichier YAML. Pour le code Go généré par l'IA, les règles critiques sont la vérification des valeurs d'erreur retournées et la détection des schémas de sécurité : les deux catégories d'échec que les modèles d'IA manquent le plus régulièrement en Go.

Installer golangci-lint

# Official binary installer:
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b "$(go env GOPATH)/bin" v2.12.2

# or via Homebrew:
brew install golangci-lint

La configuration .golangci.yml

version: "2"

linters:
  enable:
    - gosec        # security: flags hardcoded creds (G101), file path injection (G304), weak crypto (G401)
    - unused       # flags unused vars and functions — a common AI artifact in Go
    - errcheck     # AI frequently ignores error returns — this blocks it
    - govet        # catches subtle correctness bugs AI introduces
    - staticcheck  # comprehensive static analysis
    - revive       # style: catches non-idiomatic Go patterns AI writes
    - misspell     # AI occasionally misspells in comments and string literals

  settings:
    gosec:
      severity: medium
      confidence: medium
    errcheck:
      check-type-assertions: true  # check `val, ok := x.(Type)` patterns
      check-blank: true            # catch `_ = someErr` error suppression

run:
  timeout: 3m
  issues-exit-code: 1

Le schéma de gestion des erreurs de Go est explicite par conception : chaque fonction susceptible d'échouer retourne une valeur d'erreur. Les modèles d'IA comprennent cela mais le sous-estiment ; ils omettent les vérifications d'erreur dans les chemins de code non critiques. errcheck transforme cette omission en échec de lint.

gosec est le linter de sécurité. Pour le code IA, il détecte les schémas que l'IA a repris de tutoriels Go antérieurs à 2020 : génération de nombres aléatoires non sécurisée (G404), fonctions de hachage non sécurisées (G401), problèmes de permissions de fichiers (G306). Ce sont les erreurs que vous ne repérez pas en revue de code parce qu'elles paraissent syntaxiquement normales.

Exécuter golangci-lint

golangci-lint run ./...            # lint all packages
golangci-lint run --fix ./...      # auto-fix where possible

Comment relier le linter aux hooks pre-commit

Un hook pre-commit lance le lint avant chaque git commit et bloque le commit si le lint échoue. Cela signifie que l'agent IA ne peut pas commiter du code qui enfreint les règles que vous avez configurées. Il doit d'abord corriger les violations.

Lefthook est l'option recommandée. Il est multiplateforme, rapide, et possède un schéma de configuration qui fonctionne spécifiquement avec l'application par les agents IA (couverte dans la section suivante).

Lefthook

npm install --save-dev lefthook
npx lefthook install

lefthook.yml:

pre-commit:
  parallel: true
  commands:
    lint-python:
      glob: "*.py"
      run: ruff check {staged_files} --fix
    lint-js-ts:
      glob: "*.{js,ts,tsx}"
      run: npx eslint {staged_files} --fix
    lint-go:
      glob: "*.go"
      run: golangci-lint run {staged_files}
  fail_text: |
    Lint failed. For AI Agents: fix all lint violations before committing.
    Do not use --no-verify to bypass this gate.

Le message fail_text est lu par les agents IA quand un commit échoue. Le schéma est documenté dans l'article de Liam Bigelow sur l'application du lint avec Lefthook pour Claude Code. Cela seul n'arrêtera pas un agent déterminé, mais cela lui donne la bonne instruction suivante (« corriger les violations de lint ») au lieu de le laisser déduire un contournement.

Alternative : pre-commit (configurations Python uniquement)

Si vous êtes sur une stack uniquement Python et préférez le framework pre-commit :

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.15.17
    hooks:
      - id: ruff-check
        args: [--fix]
      - id: ruff-format

Cursor : linting en ligne via le hook afterFileEdit

Si vous utilisez Cursor, vous pouvez déclencher le lint immédiatement quand l'IA modifie un fichier, avant même qu'elle tente un commit. Créez .cursor/hooks.json à la racine de votre projet :

{
  "hooks": {
    "afterFileEdit": [
      {
        "match": "*.py",
        "run": "ruff check {file} --fix"
      },
      {
        "match": "*.{ts,tsx,js}",
        "run": "npx eslint {file} --fix"
      },
      {
        "match": "*.go",
        "run": "golangci-lint run {file}"
      }
    ]
  }
}

Cela se déclenche chaque fois que l'IA de Cursor modifie un fichier. L'agent reçoit le retour du lint en ligne, avant de considérer la tâche comme terminée, de sorte que la plupart des violations sont corrigées avant d'atteindre la barrière pre-commit.

Comment rendre la barrière plus difficile à contourner pour les agents IA

Diagram of four enforcement layers (CLAUDE.md policy, Claude Code deny rule, PreToolUse hook, and CI backstop) blocking an AI agent from skipping the lint gate with --no-verify

Quatre couches d'application rendent bien plus difficile pour un agent IA de contourner la barrière pre-commit avec --no-verify. Chacune cible une surface d'attaque différente : la politique dans CLAUDE.md, une règle de refus de Claude Code, un hook PreToolUse, et un rempart en CI. Considérez-les comme un dispositif en couches, et non comme quatre garanties indépendantes.

Les agents IA passent parfois --no-verify à git commit pour esquiver les hooks pre-commit quand le lint échoue et que l'agent a décidé que les échecs sont « sans rapport avec ses modifications ». La décision n'est pas toujours fausse, mais vous ne devriez pas laisser l'agent la prendre unilatéralement. Tout l'intérêt de la barrière de lint, c'est qu'un humain a défini la politique ; le rôle de l'agent est de la respecter, pas de la contourner.

Voici chaque couche et la surface d'attaque qu'elle couvre.

Couche 1 : documenter la politique dans CLAUDE.md

## Linting Policy

NEVER use `git commit --no-verify`. All commits must pass pre-commit hooks.
Pre-commit hooks run lint. Fix lint violations before committing. Do not treat
lint failures as unrelated to your changes — they may not be, and you don't get
to decide that.

Claude Code lit CLAUDE.md au démarrage de la session. Ce n'est pas de l'application ; l'agent peut toujours tenter le contournement. Mais cela supprime l'option « je ne savais pas » et fixe une politique claire que l'agent doit activement choisir d'enfreindre, ce qu'il est moins susceptible de faire que de déduire un contournement à partir du silence.

Couche 2 : règle de refus de Claude Code

Ajoutez à .claude/settings.json :

{
  "permissions": {
    "deny": [
      "Bash(git commit --no-verify*)"
    ]
  }
}

Cela bloque l'invocation explicite. Une limite : la règle de refus utilise une correspondance par préfixe, elle ne détecte donc --no-verify qu'immédiatement après commit. Un agent suffisamment inventif pourrait structurer l'appel différemment. Ne vous reposez pas sur cela seul.

Couche 3 : hook PreToolUse

Installez le paquet block-no-verify :

npm install --save-dev block-no-verify

Configurez-le ensuite comme un hook PreToolUse dans les réglages de Claude Code. Il se déclenche avant chaque appel d'outil et examine les arguments à la recherche de --no-verify sur six sous-commandes git, pas seulement commit. Il se termine avec un code non nul pour bloquer l'appel avant son exécution.

Le manuel est pydevtools.com sans détour à ce sujet : « la couche de hook est la seule qui applique la règle de façon fiable ». Utilisez-la avec les autres couches ; ne traitez aucun hook local comme la garantie complète.

Couche 4 : rempart en CI

La CI s'exécute sur le serveur, où l'agent n'a aucun shell pour y passer des drapeaux :

# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Lint Python
        run: |
          pip install ruff
          ruff check . --output-format github

      - name: Lint JS/TS
        run: npx eslint . --max-warnings 0

      - name: Lint Go
        uses: golangci/golangci-lint-action@v9
        with:
          version: v2.12.2

Les agents IA ne peuvent pas passer --no-verify à la CI. GitHub Actions s'exécute indépendamment de ce que l'agent a fait en local. Si un commit est passé d'une manière ou d'une autre avec un lint en échec, la CI l'intercepte avant qu'il ne soit fusionné.

C'est le dernier rempart.

Bonus : le serveur MCP ESLint

Si vous utilisez Claude Code, il existe une couche proactive qui réduit la fréquence à laquelle vous atteignez la barrière.

Le serveur MCP ESLint (@eslint/mcp) intègre ESLint directement dans la boucle d'outils de l'agent. L'agent peut interroger ESLint pendant la tâche, avant de tenter un commit. Installez-le globalement :

npm install -g @eslint/mcp@latest

Ajoutez à .claude/settings.json :

{
  "mcpServers": {
    "eslint": {
      "command": "npx",
      "args": ["@eslint/mcp@latest"]
    }
  }
}

Une fois cela configuré, l'agent peut interroger ESLint pendant la tâche. L'agent reçoit le retour du lint en ligne, et certaines violations peuvent être corrigées avant d'atteindre le hook. Cela ne remplace pas la barrière, cela réduit le bruit sur la barrière.

Foire aux questions

Dois-je configurer les trois linters si je ne travaille que dans un seul langage ?

Non. Configurez le linter de votre langage principal et la couche d'application. Si votre stack est uniquement TypeScript, configurez ESLint et passez Ruff et golangci-lint. La section de prévention du contournement par l'agent s'applique quel que soit le langage que vous lintez.

Ces configurations vont-elles casser ma base de code existante au premier passage ?

Presque certainement, et c'est voulu. Lancez d'abord ruff check --fix . ou npx eslint . --fix pour corriger automatiquement les violations sûres. Ce qui reste après la correction automatique constitue la liste de revue manuelle : casts no-explicit-any, fonctions qui dépassent 50 lignes, gestion d'erreur manquante. Traitez-les progressivement. N'ajoutez pas de règles ignore pour éviter d'avoir à les régler.

Biome est-il un remplaçant d'ESLint à ce stade ?

Biome v2.5.0 (sorti en juin 2026) est compétitif sur le formatage et les règles de lint de base. Il est plus rapide qu'ESLint et n'exige aucune surcharge de configuration si vous l'installez via bun x ultracite@latest init. Pour les équipes qui veulent un outil unique et n'ont pas besoin de toute la profondeur de règles de @typescript-eslint, Biome est un choix raisonnable. Pour les règles spécifiques à l'IA de ce guide (max-params, no-magic-numbers, max-lines-per-function avec des seuils ciblés sur l'IA), ESLint avec @typescript-eslint offre encore une couverture supérieure. Vous pouvez utiliser les deux : Biome pour le formatage et le lint de base, ESLint pour les règles spécifiques à l'IA.

Que faire si mon agent IA régénère sans cesse les mêmes violations de lint ?

L'agent contourne la règle au lieu de corriger le problème de fond. Pour no-explicit-any, cela signifie ajouter une assertion de type au lieu de définir le type réel. Pour max-lines-per-function, cela signifie extraire une fonction d'aide qui ne sert à rien mais fait passer le nombre de lignes sous le seuil. Aucune de ces solutions ne passe la revue de code. La règle de lint a détecté le symptôme ; la cause profonde est le prompt de l'agent. Affinez le prompt pour préciser la contrainte de type ou la décomposition attendue ; l'agent suivra des consignes de structure explicites de façon plus fiable que des règles implicites. Si vous utilisez un dispositif d'agent plus élaboré, cantonner le travail à un sous-agent dédié dont les contraintes sont intégrées à ses instructions tient généralement mieux qu'un seul prompt large.

Le serveur MCP ESLint remplace-t-il la barrière pre-commit ?

Non. Il réduit la fréquence à laquelle l'agent génère du code qui échoue à la barrière. La barrière s'exécute toujours à chaque commit. La vérification en ligne du serveur MCP et l'application par le hook pre-commit sont complémentaires, alors n'enlevez ni l'une ni l'autre.

Share

Plus d'articles du blog

Continuez la lecture.

Prêt à déployer ? À partir de 2,48 $/mois.

Cloud indépendant, depuis 2008. AMD EPYC, NVMe, 40 Gbps. Remboursement sous 14 jours.