Chargement...

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 :


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 ?


Performance et bonnes pratiques


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