Skip to content

Latest commit

 

History

History
422 lines (304 loc) · 16.3 KB

File metadata and controls

422 lines (304 loc) · 16.3 KB

🔝 Retour au Sommaire

32.1.1 — Configuration .clang-tidy

Introduction

L'exécution de clang-tidy sans configuration explicite active un jeu de checks par défaut limité. Pour exploiter pleinement l'outil, il faut sélectionner les checks adaptés à son projet, configurer leurs options, et partager cette configuration avec l'ensemble de l'équipe. C'est le rôle du fichier .clang-tidy : un fichier de configuration au format YAML, placé à la racine du projet et versionné dans Git, qui définit exactement quels checks sont actifs, comment ils se comportent, et quels warnings sont promus en erreurs.


Emplacement et résolution

Lorsque clang-tidy analyse un fichier source, il remonte l'arborescence des répertoires à partir du fichier analysé jusqu'à la racine du système de fichiers, et utilise le premier fichier .clang-tidy rencontré. Ce mécanisme de résolution hiérarchique permet d'avoir des configurations différentes par sous-répertoire.

mon_projet/
├── .clang-tidy              ← Configuration principale du projet
├── src/
│   ├── core/
│   │   └── parser.cpp       ← Utilise .clang-tidy de la racine
│   └── legacy/
│       ├── .clang-tidy       ← Configuration spécifique (checks assouplis)
│       └── old_module.cpp    ← Utilise .clang-tidy de src/legacy/
├── tests/
│   ├── .clang-tidy           ← Configuration spécifique (moins stricte)
│   └── test_parser.cpp       ← Utilise .clang-tidy de tests/
└── third_party/
    ├── .clang-tidy            ← Configuration minimale ou vide
    └── ...                    ← Code tiers non analysé

Ce mécanisme est particulièrement utile dans trois situations :

  • Code legacy : un sous-répertoire contenant du code ancien peut avoir une configuration plus permissive pendant sa migration progressive, sans relâcher les exigences sur le code récent.
  • Tests : le code de test a souvent des conventions différentes (magic numbers acceptés, variables inutilisées dans les fixtures). Un .clang-tidy dédié dans tests/ évite de polluer la configuration principale avec des exceptions.
  • Code tiers : le répertoire third_party/ ou vendor/ contient du code que vous ne maintenez pas. Un fichier .clang-tidy minimal (ou avec Checks: '-*') y désactive toute analyse.

💡 Attention : contrairement à .gitignore, les fichiers .clang-tidy ne se combinent pas par héritage. Le fichier le plus proche du source analysé est le seul utilisé — il remplace entièrement le fichier parent, il ne le complète pas. Chaque .clang-tidy doit donc être autonome et complet.


Structure du fichier

Un fichier .clang-tidy est un document YAML avec les clés suivantes :

---
# Sélection des checks
Checks: >
  -*,
  bugprone-*,
  performance-*,
  modernize-*,
  readability-*,
  clang-analyzer-*,
  -modernize-use-trailing-return-type

# Warnings traités comme des erreurs
WarningsAsErrors: ''

# Fichiers d'en-tête à filtrer
HeaderFilterRegex: '.*'

# Options spécifiques par check
CheckOptions:
  readability-identifier-naming.ClassCase: CamelCase
  readability-identifier-naming.FunctionCase: camelCase
  readability-identifier-naming.VariableCase: lower_case
  readability-identifier-naming.ConstantCase: UPPER_CASE
  readability-function-cognitive-complexity.Threshold: 25
  modernize-use-auto.MinTypeNameLength: 5

# Comportement système
SystemHeaders: false  
UseColor: true  

Détaillons chaque clé.


La clé Checks

C'est la clé la plus importante. Elle définit quels checks sont actifs via une liste de patterns séparés par des virgules. Les patterns sont évalués de gauche à droite, et chaque pattern active ou désactive les checks correspondants.

Syntaxe des patterns

Pattern Effet
bugprone-* Active tous les checks dont le nom commence par bugprone-
-bugprone-* Désactive tous les checks bugprone-*
modernize-use-nullptr Active ce check spécifique
-modernize-use-trailing-return-type Désactive ce check spécifique
* Active tous les checks
-* Désactive tous les checks

Ordre d'évaluation

Les patterns sont appliqués séquentiellement. Le dernier pattern qui correspond à un check détermine s'il est actif ou inactif. Ce mécanisme permet une logique « tout activer sauf » ou « tout désactiver sauf » :

# Stratégie "opt-in" : partir de rien et activer explicitement
Checks: >
  -*,
  bugprone-*,
  performance-*

Ici, -* désactive d'abord tout, puis bugprone-* et performance-* réactivent ces deux catégories. Seuls les checks bugprone et performance sont actifs.

# Stratégie "opt-out" : tout activer et désactiver les indésirables
Checks: >
  *,
  -llvmlibc-*,
  -fuchsia-*,
  -altera-*,
  -modernize-use-trailing-return-type,
  -readability-identifier-length

Ici, * active tout, puis les patterns négatifs désactivent les catégories non pertinentes (checks spécifiques à des projets comme LLVM libc, Fuchsia, Altera) et les checks jugés trop bruyants pour ce projet.

Syntaxe multi-lignes YAML

Le caractère > en YAML introduit un folded scalar : les retours à la ligne sont convertis en espaces. Cela permet d'écrire la liste de checks sur plusieurs lignes pour la lisibilité, tout en produisant une seule chaîne au final :

# Ces deux écritures sont équivalentes :

# Multi-lignes (recommandé pour la lisibilité)
Checks: >
  -*,
  bugprone-*,
  performance-*

# Ligne unique
Checks: '-*,bugprone-*,performance-*'

La version multi-lignes est fortement recommandée pour la maintenabilité : chaque catégorie ou exception est visible sur sa propre ligne, ce qui facilite les revues de code et le suivi des modifications dans Git.


La clé WarningsAsErrors

Cette clé utilise la même syntaxe de patterns que Checks pour promouvoir certains warnings en erreurs. Lorsqu'un check promu détecte un problème, clang-tidy retourne un code de sortie non nul, ce qui fait échouer le pipeline CI.

# Promouvoir les bugs graves en erreurs
WarningsAsErrors: >
  bugprone-use-after-move,
  bugprone-dangling-handle,
  clang-analyzer-core.*,
  clang-analyzer-cplusplus.NewDeleteLeaks

Cette mécanique permet une adoption progressive. Au début, tous les diagnostics sont des warnings : les développeurs voient les problèmes mais ne sont pas bloqués. Progressivement, les checks les plus critiques sont promus en erreurs, rendant impossible l'introduction de ces bugs dans la branche principale.

# Stratégie agressive : tous les checks actifs sont des erreurs
WarningsAsErrors: '*'

Cette stratégie « zéro warning » est la plus stricte. Elle est adaptée aux nouveaux projets qui partent sur une base saine. Pour les projets existants avec des centaines de warnings, elle serait bloquante — la stratégie progressive est alors préférable.


La clé HeaderFilterRegex

Par défaut, clang-tidy ne signale les diagnostics que dans les fichiers source (.cpp) directement analysés, pas dans les headers inclus. La clé HeaderFilterRegex contrôle quels headers sont inclus dans l'analyse via une expression régulière appliquée au chemin du fichier :

# Analyser uniquement les headers du projet (pas les headers système ni tiers)
HeaderFilterRegex: 'src/|include/'

# Analyser tous les headers (y compris tiers — bruyant)
HeaderFilterRegex: '.*'

# Analyser les headers correspondant au nom du projet
HeaderFilterRegex: 'mon_projet/'

La configuration recommandée filtre les headers pour n'inclure que ceux du projet. Analyser les headers système ou des librairies tierces produit un flot de diagnostics sur du code que vous ne contrôlez pas.


La clé CheckOptions

Beaucoup de checks sont paramétrables. La clé CheckOptions permet de personnaliser leur comportement. Les options sont spécifiées sous forme de paires clé-valeur, où la clé suit le format nom-du-check.NomOption :

Convention de nommage

Le check readability-identifier-naming est l'un des plus configurables. Il permet de définir une convention de nommage complète pour chaque catégorie d'identifiant :

CheckOptions:
  # Classes et structs en CamelCase
  readability-identifier-naming.ClassCase: CamelCase
  readability-identifier-naming.StructCase: CamelCase

  # Fonctions et méthodes en camelCase
  readability-identifier-naming.FunctionCase: camelCase
  readability-identifier-naming.MethodCase: camelCase

  # Variables locales en snake_case
  readability-identifier-naming.VariableCase: lower_case
  readability-identifier-naming.ParameterCase: lower_case

  # Membres privés avec suffixe _
  readability-identifier-naming.PrivateMemberCase: lower_case
  readability-identifier-naming.PrivateMemberSuffix: '_'

  # Constantes et enums en UPPER_CASE
  readability-identifier-naming.ConstantCase: UPPER_CASE
  readability-identifier-naming.EnumConstantCase: UPPER_CASE

  # Namespaces en lower_case
  readability-identifier-naming.NamespaceCase: lower_case

  # Template parameters en CamelCase
  readability-identifier-naming.TemplateParameterCase: CamelCase

Les valeurs acceptées pour les styles de casse sont : lower_case, UPPER_CASE, camelCase, CamelCase, camel_Snake_Case, Camel_Snake_Case, aNy_CasE.

Seuils de complexité

CheckOptions:
  # Seuil de complexité cognitive (défaut : 25)
  readability-function-cognitive-complexity.Threshold: 30

  # Longueur de ligne maximale pour readability-function-size
  readability-function-size.LineThreshold: 100

  # Nombre maximal de paramètres
  readability-function-size.ParameterThreshold: 6

  # Taille minimale du nom de type pour modernize-use-auto
  modernize-use-auto.MinTypeNameLength: 5

Options d'analyse

CheckOptions:
  # Activer l'analyse des expressions dans bugprone-assert-side-effect
  bugprone-assert-side-effect.AssertMacros: 'assert,ASSERT,Q_ASSERT'

  # Types considérés comme "trivialement copiables" pour performance-*
  performance-unnecessary-value-param.AllowedTypes: 'std::string_view;std::span'

  # Inclure les fonctions de test dans cert-err58-cpp
  cert-err58-cpp.CheckFunctionsWithoutThrow: true

Pour découvrir les options disponibles pour un check, consultez la documentation LLVM du check concerné. Chaque page de documentation liste les options avec leurs valeurs par défaut.


La clé SystemHeaders

# false (défaut) : ne pas analyser les headers système
SystemHeaders: false

Lorsque cette clé est à true, clang-tidy analyse également les headers système (/usr/include/, headers de la STL). C'est presque toujours indésirable — les headers système déclenchent des centaines de diagnostics hors de votre contrôle. Conservez la valeur par défaut false.


Exemples de configurations complètes

Nouveau projet C++23

Configuration stricte pour un projet qui démarre sur une base saine :

---
Checks: >
  -*,
  bugprone-*,
  clang-analyzer-*,
  concurrency-*,
  cppcoreguidelines-*,
  misc-*,
  modernize-*,
  performance-*,
  readability-*,
  -modernize-use-trailing-return-type,
  -readability-identifier-length,
  -cppcoreguidelines-avoid-magic-numbers,
  -readability-magic-numbers,
  -misc-non-private-member-variables-in-classes

WarningsAsErrors: >
  bugprone-*,
  clang-analyzer-*

HeaderFilterRegex: 'src/|include/'

CheckOptions:
  readability-identifier-naming.ClassCase: CamelCase
  readability-identifier-naming.FunctionCase: lower_case
  readability-identifier-naming.VariableCase: lower_case
  readability-identifier-naming.PrivateMemberSuffix: '_'
  readability-identifier-naming.ConstantCase: UPPER_CASE
  readability-identifier-naming.NamespaceCase: lower_case
  readability-function-cognitive-complexity.Threshold: 25
  modernize-use-auto.MinTypeNameLength: 5
  performance-move-const-arg.CheckTriviallyCopyableMove: false

Les catégories bugprone-* et clang-analyzer-* sont promues en erreurs : aucun bug de ces catégories ne peut être introduit. Les checks cosmétiques comme modernize-use-trailing-return-type (controversial) et readability-identifier-length (trop bruyant sur les variables de boucle) sont désactivés.

Projet legacy en migration

Configuration plus permissive, adaptée à un projet existant où l'activation de tous les checks produirait des milliers de warnings :

---
Checks: >
  -*,
  bugprone-*,
  clang-analyzer-core.*,
  clang-analyzer-cplusplus.*,
  performance-unnecessary-value-param,
  performance-unnecessary-copy-initialization,
  performance-for-range-copy,
  modernize-use-nullptr,
  modernize-use-override,
  modernize-use-emplace,
  readability-redundant-string-cstr

WarningsAsErrors: ''

HeaderFilterRegex: 'src/'

CheckOptions:
  readability-function-cognitive-complexity.Threshold: 50

Seuls les checks les plus impactants sont activés. Aucun n'est promu en erreur — la stratégie est de corriger progressivement. Le seuil de complexité cognitive est relevé à 50 (contre 25 par défaut) pour ne pas signaler toutes les fonctions héritées du code legacy. À mesure que le code est refactorisé, la configuration est durcie.

Répertoire de tests

Configuration allégée pour le code de test :

---
Checks: >
  -*,
  bugprone-*,
  clang-analyzer-*,
  -bugprone-easily-swappable-parameters,
  -cppcoreguidelines-avoid-magic-numbers,
  -readability-magic-numbers,
  -readability-function-cognitive-complexity

WarningsAsErrors: ''

HeaderFilterRegex: 'tests/'

CheckOptions: {}

Les checks sur les magic numbers et la complexité des fonctions sont désactivés — les tests utilisent légitimement des valeurs littérales et les fixtures de test peuvent être longues. Les checks bugprone-* et clang-analyzer-* restent actifs car les bugs dans le code de test sont tout aussi problématiques que dans le code de production.

Désactivation pour le code tiers

---
Checks: '-*'

Ce fichier d'une seule ligne, placé dans third_party/, désactive complètement l'analyse. C'est la configuration recommandée pour le code tiers : vous ne le maintenez pas, les diagnostics ne sont pas actionnables.


Vérifier la configuration active

Pour vérifier quels checks sont effectivement actifs avec la configuration courante :

clang-tidy --list-checks -p build/ src/main.cpp

Cette commande lit le .clang-tidy applicable au fichier spécifié et affiche la liste des checks actifs. C'est un outil de diagnostic indispensable lorsqu'un check semble ne pas se déclencher : peut-être est-il désactivé par un pattern négatif plus tardif dans la chaîne Checks.

Pour afficher la configuration complète telle qu'elle est interprétée :

clang-tidy --dump-config -p build/ src/main.cpp

Cette commande affiche le YAML complet résolu, incluant toutes les options par défaut de chaque check actif. C'est particulièrement utile pour découvrir les options non documentées ou les valeurs par défaut.


Versionnement et revue de la configuration

Le fichier .clang-tidy fait partie intégrante du code source du projet. Il doit être :

  • Versionné dans Git au même titre que le CMakeLists.txt ou le Makefile.
  • Revu en code review lors de chaque modification. L'activation ou la désactivation d'un check affecte la qualité du code sur l'ensemble du projet — c'est une décision d'équipe, pas une préférence individuelle.
  • Documenté par des commentaires YAML expliquant les choix non évidents, en particulier les désactivations :
Checks: >
  -*,
  bugprone-*,
  performance-*,
  # Désactivé : trop de faux positifs sur nos fonctions template
  -bugprone-easily-swappable-parameters,
  # Désactivé : incompatible avec notre convention de trailing return types
  -modernize-use-trailing-return-type

La stabilité de la configuration est importante. Changer fréquemment les checks actifs crée du bruit dans le diff et frustre les développeurs. La stratégie recommandée est de définir la configuration en début de projet (ou lors d'une session dédiée pour un projet existant), puis de la faire évoluer de façon incrémentale et délibérée : un check ajouté par sprint, avec les corrections associées dans un commit dédié.

⏭️ Checks recommandés