Analyse de code avec Roslyn Compiler API en C#
Apprenez l’analyse de code en C# avec Roslyn, y compris les arbres syntaxiques et la génération de code.
Sur la plateforme .NET, Roslyn (Microsoft.CodeAnalysis) fournit les compilateurs C# et VB entièrement écrits en code managé.
L’API du compilateur Roslyn permet de lire, analyser, modifier et même recompiler le code source de manière programmatique.
Elle permet ainsi de créer des outils d’analyse statique, des correcteurs de code automatiques, des linters ou des extensions IDE personnalisées.
Qu’est-ce que l’API du compilateur Roslyn ?
Roslyn est le compilateur C# exposé sous forme d’API pour les développeurs.
Elle permet d’effectuer dans votre propre code toutes les opérations que le compilateur Visual Studio ou dotnet exécute habituellement (analyse, compilation, etc.).
Elle est composée des couches principales suivantes :
- Syntax API : donne accès à la structure syntaxique du code source sous forme d’arbre.
- Semantic API : fournit des informations sémantiques telles que les types de variables, les symboles et le contexte de compilation.
- Compilation API : gère la compilation du code, l’analyse des avertissements/erreurs et la génération du code IL (Intermediate Language).
Installation
Pour utiliser l’API Roslyn, ajoutez le package NuGet Microsoft.CodeAnalysis.CSharp :
dotnet add package Microsoft.CodeAnalysis.CSharp
Créer un arbre syntaxique à partir du code source
La première étape consiste à convertir un code C# en un arbre syntaxique (Syntax Tree). Cet arbre représente la structure du code sous forme de nœuds hiérarchiques.
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
class Program
{
static void Main()
{
string code = @"
using System;
class Bonjour
{
static void Main()
{
Console.WriteLine(""Bonjour Roslyn!"");
}
}";
SyntaxTree arbre = CSharpSyntaxTree.ParseText(code);
var racine = arbre.GetRoot();
Console.WriteLine($"Nœud racine : {racine.Kind()}");
foreach (var node in racine.DescendantNodes())
Console.WriteLine(node.Kind());
}
}
La sortie affichera des types de nœuds tels que ClassDeclaration, MethodDeclaration ou InvocationExpression.
Modèle sémantique : analyse du sens du code
Le SemanticModel fournit des informations détaillées sur les types de variables, les signatures de méthodes et les symboles. Par exemple, il peut identifier à quelle classe appartient une méthode ou déterminer le type d’une variable.
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program
{
static void Main()
{
string code = @"
using System;
class Test
{
static void Main()
{
string message = ""Salut!"";
Console.WriteLine(message);
}
}";
SyntaxTree arbre = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("TestCompilation")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(arbre);
var modele = compilation.GetSemanticModel(arbre);
var racine = arbre.GetRoot();
var variable = racine.DescendantNodes().OfType<VariableDeclaratorSyntax>().First();
var symbole = modele.GetDeclaredSymbol(variable);
Console.WriteLine($"Nom de la variable : {symbole.Name}, Type : {symbole.GetSymbolType()?.Name}");
}
}
Cet exemple permet d’obtenir le type (string) de la variable message.
Roslyn extrait cette information depuis la table des symboles — il ne s’agit pas d’une simple analyse textuelle.
Syntax Rewriter : modifier le code
Roslyn permet non seulement d’analyser le code, mais aussi de le transformer (refactoring).
Vous pouvez utiliser la classe CSharpSyntaxRewriter pour modifier ou supprimer certains nœuds de l’arbre syntaxique.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
class UppercaseRewriter : CSharpSyntaxRewriter
{
public override SyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node)
{
if (node.IsKind(SyntaxKind.StringLiteralExpression))
{
string nouveau = node.Token.ValueText.ToUpperInvariant();
return SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal(nouveau));
}
return base.VisitLiteralExpression(node);
}
}
class Program
{
static void Main()
{
string code = @"class A { void M() { System.Console.WriteLine(""bonjour""); } }";
SyntaxTree arbre = CSharpSyntaxTree.ParseText(code);
var rewriter = new UppercaseRewriter();
var nouvelleRacine = rewriter.Visit(arbre.GetRoot());
Console.WriteLine(nouvelleRacine.ToFullString());
}
}
Cet exemple transforme toutes les chaînes de caractères en majuscules.
Ainsi, "bonjour" devient "BONJOUR".
Diagnostics : analyse des erreurs et avertissements
L’objet Compilation peut renvoyer les erreurs et avertissements survenus lors de la compilation du code.
Cela permet de définir des règles personnalisées et de créer des outils d’analyse statique.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Linq;
class Program
{
static void Main()
{
var code = "class A { void M() { int x = \"faux\"; } }";
var arbre = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("Analyse")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(arbre);
var diagnostics = compilation.GetDiagnostics();
foreach (var d in diagnostics)
Console.WriteLine($"{d.Id}: {d.GetMessage()}");
}
}
Sortie : CS0029 : impossible de convertir implicitement le type 'string' en 'int'
Cela permet de capturer les erreurs du compilateur ou d’appliquer vos propres règles d’analyse.
Exemple : Analyse des « variables non utilisées »
L’exemple suivant utilise l’API Roslyn pour détecter les variables qui sont déclarées mais jamais utilisées dans un fichier. Il fonctionne de manière similaire à l’avertissement vert « non utilisé » de Visual Studio.
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
class Program
{
static void Main()
{
string code = @"
class Test
{
void Methode()
{
int x = 10;
int y = 20;
Console.WriteLine(x);
}
}";
var arbre = CSharpSyntaxTree.ParseText(code);
var compilation = CSharpCompilation.Create("Analyse")
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(arbre);
var modele = compilation.GetSemanticModel(arbre);
var racine = arbre.GetRoot();
var variables = racine.DescendantNodes().OfType<VariableDeclaratorSyntax>();
foreach (var v in variables)
{
var symbole = modele.GetDeclaredSymbol(v);
var utilisations = racine.DescendantNodes()
.OfType<IdentifierNameSyntax>()
.Count(i => i.Identifier.Text == v.Identifier.Text);
if (utilisations <= 1)
Console.WriteLine($"Avertissement : la variable '{v.Identifier.Text}' n'est pas utilisée.");
}
}
}
Sortie : Avertissement : la variable 'y' n'est pas utilisée.
Cet exemple simple illustre le fonctionnement des outils d’analyse statique du code.
Que peut-on développer avec Roslyn ?
- Outils d’analyse de code statique (par ex. règles de style, de sécurité ou de performance)
- Code Analyzer & Code Fix Provider (extensions Visual Studio)
- Source Generators (génération de code au moment de la compilation)
- Outils de refactoring ou de mise en forme automatique du code
- Transformation et réécriture de code (par ex. remplacement d’anciennes API)
Performance et bonnes pratiques
- Les analyses Roslyn peuvent être coûteuses en CPU et en mémoire ; pour les grands projets, privilégiez l’analyse incrémentale.
- Les objets
SyntaxTreesont immuables ; utilisez les méthodesWith...ou un rewriter pour appliquer des modifications. - Évitez les parcours d’arbre inutiles dans les grandes bases de code en utilisant des requêtes filtrées (
OfType<T>). - Si vous développez des extensions IDE, exécutez les analyses de manière asynchrone en arrière-plan.
TL;DR
- L’API du compilateur Roslyn fournit une interface programmatique pour analyser, transformer et compiler du code C#.
- La Syntax API fournit la structure syntaxique (arbre de syntaxe), tandis que la Semantic API fournit les informations sémantiques (type/symbole).
- Des outils personnalisés de code analyzer ou de refactoring peuvent être développés avec cette API.
- Contrairement à la réflexion (Reflection), Roslyn se concentre sur l’analyse au moment de la compilation.
- Dans les grands projets, l’analyse incrémentale et l’optimisation des performances sont essentielles.
Articles connexes
Le concept des générateurs de code en C# (C# 9+)
Découvrez les générateurs de code en C# pour générer du code à la compilation et améliorer les performances.
Principes SOLID en C#
Application des principes SOLID en C# avec des exemples : pour un code flexible, maintenable et testable.
Réflexion et liaison tardive en C#
Apprenez la réflexion et la liaison tardive en C# pour analyser les types à l’exécution avec des exemples.