Partager via


Tutoriel : Écrire votre premier analyseur et correctif de code

Le Kit de développement logiciel (SDK) .NET Compiler Platform fournit les outils dont vous avez besoin pour créer des diagnostics personnalisés (analyseurs), des correctifs de code, des refactorisations de code et des suppresseurs de diagnostic qui ciblent du code C# ou Visual Basic. Un analyseur contient du code qui reconnaît les violations de votre règle. Votre correctif de code contient le code qui corrige la violation. Les règles que vous implémentez peuvent aller de la structure du code au style de codage aux conventions d’affectation de noms et bien plus encore. .NET Compiler Platform fournit le framework permettant d’exécuter l’analyse alors que les développeurs écrivent du code, et toutes les fonctionnalités de l’IU Visual Studio pour corriger le code : afficher des tildes dans l’éditeur, renseigner la liste d’erreurs Visual Studio, créer des suggestions « ampoule » et afficher un aperçu détaillé des corrections suggérées.

Dans ce tutoriel, vous allez explorer la création d’un analyseur et un correctif de code associé à l’aide des API Roslyn. Un analyseur est un moyen d’effectuer une analyse du code source et de signaler un problème à l’utilisateur. Si vous le souhaitez, un correctif de code peut être associé à l’analyseur pour représenter une modification du code source de l’utilisateur. Ce tutoriel crée un analyseur qui recherche des déclarations de variables locales qui peuvent être déclarées à l’aide du const modificateur, mais qui ne le sont pas. Le correctif de code associé modifie ces déclarations pour ajouter le const modificateur.

Conditions préalables

Vous devez installer le Kit de développement logiciel (SDK) .NET Compiler Platform via Visual Studio Installer :

Instructions d’installation - Visual Studio Installer

Il existe deux façons de trouver le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET dans Visual Studio Installer :

Installer à l’aide de l’affichage Visual Studio Installer - Charges de travail

Le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET n’est pas automatiquement sélectionné dans le cadre de la charge de travail de développement d’extension Visual Studio. Vous devez le sélectionner en tant que composant facultatif.

  1. Exécuter Visual Studio Installer
  2. Sélectionnez Modifier
  3. Choisissez la charge de travail Développement d’extensions Visual Studio.
  4. Ouvrez le nœud de développement d’extension Visual Studio dans l’arborescence récapitulative.
  5. Cochez la case pour le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET. Vous le trouverez en dernier sous les composants facultatifs.

Si vous le souhaitez, vous souhaiterez également que l’éditeur DGML affiche des graphiques dans le visualiseur :

  1. Ouvrez le nœud Composants individuels dans l’arborescence récapitulative.
  2. Cochez la case pour l’éditeur DGML

Installer à l’aide de l’onglet Visual Studio Installer - Composants individuels

  1. Exécuter Visual Studio Installer
  2. Sélectionnez Modifier
  3. Sélectionnez l’onglet Composants individuels
  4. Cochez la case pour le Kit de développement logiciel (SDK) de la plateforme du compilateur .NET. Vous le trouverez en haut sous la section Compilateurs, outils de génération et runtimes .

Si vous le souhaitez, vous souhaiterez également que l’éditeur DGML affiche des graphiques dans le visualiseur :

  1. Cochez la case pour l’éditeur DGML. Vous le trouverez sous la section Outils de code .

Il existe plusieurs étapes pour créer et valider votre analyseur :

  1. Créez la solution.
  2. Inscrivez le nom et la description de l’analyseur.
  3. Avertissements et recommandations de l’analyseur de rapports.
  4. Implémentez le correctif de code pour accepter les recommandations.
  5. Améliorez l’analyse par le biais de tests unitaires.

Créer la solution

  • Dans Visual Studio, choisissez Fichier > nouveau > projet... pour afficher la boîte de dialogue Nouveau projet.
  • Sous Extensibilité Visual C#>, choisissez Analyseur avec correctif de code (.NET Standard).
  • Nommez votre projet « MakeConst », puis cliquez sur OK.

Remarque

Vous pouvez obtenir une erreur de compilation (MSB4062 : la tâche « CompareBuildTaskVersion » n’a pas pu être chargée). Pour résoudre ce problème, mettez à jour les packages NuGet dans la solution avec le Gestionnaire de package NuGet ou utilisez-le Update-Package dans la fenêtre console du Gestionnaire de package.

Explorer le modèle d’analyseur

L’analyseur avec le modèle de correctif de code crée cinq projets :

  • MakeConst, qui contient l’analyseur.
  • MakeConst.CodeFixes, qui contient le correctif de code.
  • MakeConst.Package, qui est utilisé pour produire le package NuGet pour l’analyseur et le correctif de code.
  • MakeConst.Test, qui est un projet de test unitaire.
  • MakeConst.Vsix, qui est le projet de démarrage par défaut qui démarre une deuxième instance de Visual Studio qui a chargé votre nouvel analyseur. Appuyez sur F5 pour démarrer le projet VSIX.

Remarque

Les analyseurs doivent cibler .NET Standard 2.0, car ils peuvent s’exécuter dans un environnement .NET Core (builds de ligne de commande) et un environnement .NET Framework (Visual Studio).

Conseil / Astuce

Lorsque vous exécutez votre analyseur, vous démarrez une deuxième copie de Visual Studio. Cette deuxième copie utilise une ruche de Registre différente pour stocker les paramètres. Cela vous permet de différencier les paramètres visuels dans les deux copies de Visual Studio. Vous pouvez choisir un thème différent pour l’exécution expérimentale de Visual Studio. En outre, ne parcourez pas vos paramètres ni connectez-vous à votre compte Visual Studio à l’aide de l’exécution expérimentale de Visual Studio. Cela maintient les paramètres différents.

La ruche inclut non seulement l’analyseur en cours de développement, mais aussi tous les analyseurs précédents ouverts. Pour réinitialiser la ruche Roslyn, vous devez la supprimer manuellement de %LocalAppData%\Microsoft\VisualStudio. Le nom du dossier de la ruche Roslyn se terminera par Roslyn, par exemple 16.0_9ae182f9Roslyn. Notez que vous devrez peut-être nettoyer la solution et la reconstruire après la suppression de la ruche.

Dans la deuxième instance de Visual Studio que vous venez de démarrer, créez un projet d’application console C# (n’importe quelle infrastructure cible fonctionnera - les analyseurs fonctionnent au niveau source.) Pointez sur le jeton avec un soulignement ondulé et le texte d’avertissement fourni par un analyseur s’affiche.

Le modèle crée un analyseur qui signale un avertissement sur chaque déclaration de type où le nom du type contient des lettres minuscules, comme illustré dans la figure suivante :

Avertissement de rapport de l’analyseur

Le modèle fournit également un correctif de code qui modifie tout nom de type contenant des caractères minuscules en majuscules. Vous pouvez cliquer sur l’ampoule affichée avec l’avertissement pour afficher les modifications suggérées. L’acceptation des modifications suggérées met à jour le nom du type et toutes les références à ce type dans la solution. Maintenant que vous avez vu l’analyseur initial en action, fermez la deuxième instance de Visual Studio et revenez à votre projet d’analyseur.

Vous n’avez pas besoin de démarrer une deuxième copie de Visual Studio et de créer du code pour tester chaque modification de votre analyseur. Le modèle crée également un projet de test unitaire pour vous. Ce projet contient deux tests. TestMethod1 affiche le format classique d’un test qui analyse le code sans déclencher de diagnostic. TestMethod2 affiche le format d’un test qui déclenche un diagnostic, puis applique un correctif de code suggéré. Lorsque vous générez votre analyseur et correctif de code, vous allez écrire des tests pour différentes structures de code afin de vérifier votre travail. Les tests unitaires pour les analyseurs sont beaucoup plus rapides que les tests interactifs avec Visual Studio.

Conseil / Astuce

Les tests unitaires d’analyseur sont un excellent outil quand vous savez quelles constructions de code doivent et ne doivent pas déclencher votre analyseur. Le chargement de votre analyseur dans une autre copie de Visual Studio est un excellent outil pour explorer et trouver des constructions que vous n’avez peut-être pas encore pensé.

Dans ce tutoriel, vous écrivez un analyseur qui signale à l’utilisateur toutes les déclarations de variables locales qui peuvent être converties en constantes locales. Considérons par exemple le code suivant :

int x = 0;
Console.WriteLine(x);

Dans le code ci-dessus, x une valeur constante est affectée et n’est jamais modifiée. Il peut être déclaré à l’aide du const modificateur :

const int x = 0;
Console.WriteLine(x);

L’analyse pour déterminer si une variable peut être rendue constante est impliquée, nécessitant une analyse syntactique, une analyse constante de l’expression d’initialiseur et l’analyse du flux de données pour s’assurer que la variable n’est jamais écrite. La plateforme du compilateur .NET fournit des API qui facilitent l’exécution de cette analyse.

Créer des enregistrements d’analyseurs

Le modèle crée la classe initiale DiagnosticAnalyzer , dans le fichier MakeConstAnalyzer.cs . Cet analyseur initial affiche deux propriétés importantes de chaque analyseur.

  • Chaque analyseur de diagnostic doit fournir un [DiagnosticAnalyzer] attribut qui décrit le langage sur lequel il fonctionne.
  • Chaque analyseur de diagnostic doit dériver (directement ou indirectement) de la DiagnosticAnalyzer classe.

Le modèle affiche également les fonctionnalités de base qui font partie de n’importe quel analyseur :

  1. Enregistrer des actions. Les actions représentent les modifications de code qui doivent déclencher votre analyseur pour examiner le code pour détecter les violations. Lorsque Visual Studio détecte les modifications de code qui correspondent à une action inscrite, elle appelle la méthode inscrite de votre analyseur.
  2. Créez des diagnostics. Lorsque votre analyseur détecte une violation, il crée un objet de diagnostic que Visual Studio utilise pour avertir l’utilisateur de la violation.

Vous enregistrez des actions dans votre substitution de la méthode DiagnosticAnalyzer.Initialize(AnalysisContext). Dans ce tutoriel, vous allez visiter les nœuds de syntaxe à la recherche de déclarations locales et voir laquelle de ces déclarations ont des valeurs constantes. Si une déclaration peut être constante, votre analyseur crée et signale un diagnostic.

La première étape consiste à mettre à jour les constantes d'enregistrement et la méthode Initialize afin que ces constantes définissent votre analyseur « Make Const ». La plupart des constantes de chaîne sont définies dans le fichier de ressources de chaîne. Vous devez suivre cette pratique pour faciliter la localisation. Ouvrez le fichier Resources.resx pour le projet d’analyseur MakeConst . Cela affiche l’éditeur de ressources. Mettez à jour les ressources de chaîne comme suit :

  • Passez AnalyzerDescription à «Variables that are not modified should be made constants. ».
  • Passez AnalyzerMessageFormat à «Variable '{0}' can be made constant ».
  • Passez AnalyzerTitle à «Variable can be made constant ».

Lorsque vous avez terminé, l’éditeur de ressources doit apparaître comme indiqué dans la figure suivante :

Mettre à jour les ressources de chaînes de caractères

Les modifications restantes se trouvent dans le fichier d’analyseur. Ouvrez MakeConstAnalyzer.cs dans Visual Studio. Modifiez l’action inscrite d’une action qui agit sur des symboles en une qui agit sur la syntaxe. Dans la MakeConstAnalyzerAnalyzer.Initialize méthode, recherchez la ligne qui inscrit l’action sur les symboles :

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Remplacez-le par la ligne suivante :

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Après cette modification, vous pouvez supprimer la AnalyzeSymbol méthode. Cet outil examine SyntaxKind.LocalDeclarationStatement, et non les instructions SymbolKind.NamedType. Notez que AnalyzeNode a des lignes ondulées rouges en dessous. Le code que vous venez d’ajouter fait référence à une AnalyzeNode méthode qui n’a pas été déclarée. Déclarez cette méthode à l’aide du code suivant :

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Remplacez la Category valeur «Usage » dans MakeConstAnalyzer.cs comme indiqué dans le code suivant :

private const string Category = "Usage";

Rechercher des déclarations locales qui pourraient être constantes

Il est temps d’écrire la première version de la AnalyzeNode méthode. Il doit rechercher une seule déclaration locale qui pourrait être const mais ne l'est pas, comme le code suivant :

int x = 0;
Console.WriteLine(x);

La première étape consiste à rechercher des déclarations locales. Ajoutez le code suivant dans AnalyzeNodeMakeConstAnalyzer.cs :

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Ce cast réussit toujours, car votre analyseur a enregistré les modifications apportées aux déclarations locales, et uniquement les déclarations locales. Aucun autre type de nœud ne déclenche un appel à votre AnalyzeNode méthode. Ensuite, vérifiez la présence de modificateurs const dans la déclaration. Si vous les trouvez, retournez immédiatement. Le code suivant recherche les modificateurs const sur la déclaration locale :

// make sure the declaration isn't already const:
if (localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword))
{
    return;
}

Enfin, vous devez vérifier que la variable peut être const. Cela signifie de s’assurer qu’elle n’est jamais assignée après son initialisation.

Vous allez effectuer une analyse sémantique à l’aide du SyntaxNodeAnalysisContext. Vous utilisez l’argument context pour déterminer si la déclaration de variable locale peut être effectuée const. Un Microsoft.CodeAnalysis.SemanticModel représente toutes les informations sémantiques dans un seul fichier source. Vous pouvez en savoir plus dans l’article qui couvre les modèles sémantiques. Vous utiliserez l’outil Microsoft.CodeAnalysis.SemanticModel pour effectuer l’analyse du flux de données sur la déclaration locale. Ensuite, vous utilisez les résultats de cette analyse de flux de données pour vous assurer que la variable locale n’est pas écrite avec une nouvelle valeur ailleurs. Appelez le GetDeclaredSymbol membre d'extension pour récupérer le ILocalSymbol de la variable et vérifiez qu'il n'est pas contenu dans la DataFlowAnalysis.WrittenOutside collection de l'analyse de flux de données. Ajoutez le code suivant à la fin de la AnalyzeNode méthode :

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
VariableDeclaratorSyntax variable = localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
    return;
}

Le code garantit que la variable n'est pas modifiée et peut donc être rendue const. Il est temps de déclencher le diagnostic. Ajoutez le code suivant comme dernière ligne dans AnalyzeNode:

context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation(), localDeclaration.Declaration.Variables.First().Identifier.ValueText));

Vous pouvez vérifier votre progression en appuyant sur F5 pour exécuter votre analyseur. Vous pouvez charger l’application console que vous avez créée précédemment, puis ajouter le code de test suivant :

int x = 0;
Console.WriteLine(x);

L’ampoule doit apparaître et votre analyseur doit signaler un diagnostic. Toutefois, selon votre version de Visual Studio, vous verrez :

  • L’ampoule, qui utilise toujours le correctif de code généré par le modèle et vous indique qu’il peut être converti en majuscules.
  • Un message de bannière en haut de l’éditeur indiquant que « MakeConstCodeFixProvider » a rencontré une erreur et a été désactivé. Cela est dû au fait que le fournisseur de correctifs de code n’a pas encore été modifié et s’attend toujours à trouver des éléments TypeDeclarationSyntax au lieu de LocalDeclarationStatementSyntax éléments.

La section suivante explique comment écrire le correctif de code.

Écrire le correctif de code

Un analyseur peut fournir un ou plusieurs correctifs de code. Un correctif de code définit une modification qui résout le problème signalé. Pour l’analyseur que vous avez créé, vous pouvez fournir un correctif de code qui insère le mot clé const :

- int x = 0;
+ const int x = 0;
Console.WriteLine(x);

L’utilisateur le choisit dans l’interface utilisateur de l’ampoule dans l’éditeur et Visual Studio modifie le code.

Ouvrez le fichier CodeFixResources.resx et passez CodeFixTitle à «Make constant ».

Ouvrez le fichier MakeConstCodeFixProvider.cs ajouté par le modèle. Ce correctif de code est déjà câblé à l’ID de diagnostic produit par votre analyseur de diagnostic, mais il n’implémente pas encore la transformation de code appropriée.

Ensuite, supprimez la MakeUppercaseAsync méthode. Il ne s’applique plus.

Tous les fournisseurs de correctifs de code dérivent de CodeFixProvider. Ils outrepassent CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext) pour signaler les correctifs de code disponibles. Dans RegisterCodeFixesAsync, modifiez le type de nœud ancêtre que vous recherchez pour qu’il LocalDeclarationStatementSyntax corresponde au diagnostic :

var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>().First();

Ensuite, modifiez la dernière ligne pour inscrire un correctif de code. Votre correctif crée un document qui résulte de l’ajout du const modificateur à une déclaration existante :

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
    CodeAction.Create(
        title: CodeFixResources.CodeFixTitle,
        createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
        equivalenceKey: nameof(CodeFixResources.CodeFixTitle)),
    diagnostic);

Vous remarquerez des soulignements rouges dans le code que vous venez d’ajouter sur le symbole MakeConstAsync. Ajoutez une déclaration pour MakeConstAsync comme dans le code suivant :

private static async Task<Document> MakeConstAsync(Document document,
    LocalDeclarationStatementSyntax localDeclaration,
    CancellationToken cancellationToken)
{
}

Votre nouvelle MakeConstAsync méthode transforme le Document fichier source de l’utilisateur en une nouvelle Document qui contient désormais une const déclaration.

Vous créez un nouveau jeton de mot clé const à insérer au début de l’instruction de déclaration. Veillez tout d’abord à supprimer les trivia de début dans le premier jeton de l’instruction de déclaration et à les associer au jeton const. Ajoutez le code suivant à la méthode MakeConstAsync :

// Remove the leading trivia from the local declaration.
SyntaxToken firstToken = localDeclaration.GetFirstToken();
SyntaxTriviaList leadingTrivia = firstToken.LeadingTrivia;
LocalDeclarationStatementSyntax trimmedLocal = localDeclaration.ReplaceToken(
    firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));

// Create a const token with the leading trivia.
SyntaxToken constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));

Ensuite, ajoutez le const jeton à la déclaration à l’aide du code suivant :

// Insert the const token into the modifiers list, creating a new modifiers list.
SyntaxTokenList newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal = trimmedLocal
    .WithModifiers(newModifiers)
    .WithDeclaration(localDeclaration.Declaration);

Ensuite, mettez en forme la nouvelle déclaration pour qu’elle corresponde aux règles de mise en forme C#. La mise en forme de vos modifications pour correspondre au code existant crée une meilleure expérience. Ajoutez l’instruction suivante immédiatement après le code existant :

// Add an annotation to format the new local declaration.
LocalDeclarationStatementSyntax formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);

Un nouvel espace de noms est requis pour ce code. Ajoutez la directive suivante using en haut du fichier :

using Microsoft.CodeAnalysis.Formatting;

La dernière étape consiste à effectuer votre modification. Il existe trois étapes pour ce processus :

  1. Obtenir un handle pour le document existant.
  2. Créez un document en remplaçant la déclaration existante par la nouvelle déclaration.
  3. Retournez le nouveau document.

Ajoutez le code suivant à la fin de la MakeConstAsync méthode :

// Replace the old local declaration with the new local declaration.
SyntaxNode oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxNode newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);

// Return document with transformed tree.
return document.WithSyntaxRoot(newRoot);

Votre correctif de code est prêt à essayer. Appuyez sur F5 pour exécuter le projet d’analyseur dans une deuxième instance de Visual Studio. Dans la deuxième instance de Visual Studio, créez un projet d’application console C# et ajoutez quelques déclarations de variables locales initialisées avec des valeurs constantes à la méthode Main. Vous verrez qu’ils sont signalés comme avertissements comme ci-dessous.

Avertissements Can make const

Vous avez fait beaucoup de progrès. Des traits de soulignement ondulés s’affichent sous les déclarations qui peuvent devenir const. Mais il y a encore du travail à faire. Cela fonctionne correctement si vous ajoutez const aux déclarations commençant par i, puis j enfin k. Mais, si vous ajoutez le const modificateur dans un ordre différent, en commençant par k, votre analyseur crée des erreurs : k ne peut pas être déclaré const, sauf si i les j deux sont déjà const. Vous devez effectuer davantage d’analyse pour vous assurer que vous gérez les différentes façons dont les variables peuvent être déclarées et initialisées.

Construire des tests unitaires

Votre analyseur et votre correctif de code travaillent sur un cas simple d’une déclaration unique qui peut être devenir constante. Il existe de nombreuses déclarations possibles dans lesquelles cette implémentation fait des erreurs. Vous allez résoudre ces cas en travaillant avec la bibliothèque de tests unitaires écrite par le modèle. Il est beaucoup plus rapide que l’ouverture répétée d’une deuxième copie de Visual Studio.

Ouvrez le fichier MakeConstUnitTests.cs dans le projet de test unitaire. Le modèle a créé deux tests qui suivent les deux modèles courants d’un analyseur et d’un test unitaire de correctif de code. TestMethod1 affiche le modèle d’un test qui garantit que l’analyseur ne signale pas de diagnostic lorsqu’il ne le doit pas. TestMethod2 montre le modèle pour signaler un diagnostic et corriger le code.

Le modèle utilise les packages Microsoft.CodeAnalysis.Testing pour les tests unitaires.

Conseil / Astuce

La bibliothèque de test prend en charge une syntaxe de balisage spéciale, notamment :

  • [|text|]: indique qu’un diagnostic est signalé pour text. Par défaut, ce formulaire ne peut être utilisé que pour tester des analyseurs avec exactement un DiagnosticDescriptor fourni par DiagnosticAnalyzer.SupportedDiagnostics.
  • {|ExpectedDiagnosticId:text|}: indique qu’un diagnostic avec IdExpectedDiagnosticId est signalé pour text.

Remplacez les tests de modèle dans la MakeConstUnitTest classe par la méthode de test suivante :

        [TestMethod]
        public async Task LocalIntCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|int i = 0;|]
        Console.WriteLine(i);
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const int i = 0;
        Console.WriteLine(i);
    }
}
");
        }

Exécutez ce test pour vous assurer qu’il réussit. Dans Visual Studio, ouvrez l’Explorateur de tests en sélectionnant Test>Windows>Test Explorer. Ensuite, sélectionnez Exécuter tout.

Créer des tests pour les déclarations valides

En règle générale, les analyseurs doivent quitter le plus rapidement possible, en effectuant un travail minimal. Visual Studio appelle des analyseurs inscrits au fur et à mesure que l’utilisateur modifie le code. La réactivité est une exigence essentielle. Il existe plusieurs cas de test pour le code qui ne doivent pas déclencher votre diagnostic. Votre analyseur gère déjà plusieurs de ces tests. Ajoutez les méthodes de test suivantes pour représenter ces cas :

        [TestMethod]
        public async Task VariableIsAssigned_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i = 0;
        Console.WriteLine(i++);
    }
}
");
        }
        [TestMethod]
        public async Task VariableIsAlreadyConst_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        const int i = 0;
        Console.WriteLine(i);
    }
}
");
        }
        [TestMethod]
        public async Task NoInitializer_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i;
        i = 0;
        Console.WriteLine(i);
    }
}
");
        }

Ces tests réussissent, car votre analyseur gère déjà ces conditions :

  • Les variables affectées après l’initialisation sont détectées par l’analyse du flux de données.
  • Les déclarations qui sont déjà const sont filtrées en vérifiant la présence du mot-clé const.
  • Les déclarations sans initialiseur sont gérées par l’analyse du flux de données qui détecte les affectations en dehors de la déclaration.

Ensuite, ajoutez des méthodes de test pour les conditions que vous n’avez pas encore gérées :

  • Déclarations où l’initialiseur n’est pas une constante, car ils ne peuvent pas être des constantes au moment de la compilation :

            [TestMethod]
            public async Task InitializerIsNotConstant_NoDiagnostic()
            {
                await VerifyCS.VerifyAnalyzerAsync(@"
    using System;
    
    class Program
    {
        static void Main()
        {
            int i = DateTime.Now.DayOfYear;
            Console.WriteLine(i);
        }
    }
    ");
            }
    

Cela peut être encore plus compliqué, car C# autorise plusieurs déclarations en tant qu’instruction. Considérez la constante de chaîne de cas de test suivante :

        [TestMethod]
        public async Task MultipleInitializers_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int i = 0, j = DateTime.Now.DayOfYear;
        Console.WriteLine(i);
        Console.WriteLine(j);
    }
}
");
        }

La variable i peut être rendue constante, mais la variable j ne peut pas. Par conséquent, cette déclaration ne peut pas être faite comme déclaration const.

Réexécutez vos tests et vous verrez que ces deux derniers cas de test échouent.

Veuillez mettre à jour votre analyseur pour qu'il ignore les déclarations correctes

Vous avez besoin d’améliorations apportées à la méthode de AnalyzeNode votre analyseur pour filtrer le code qui correspond à ces conditions. Il s’agit de toutes les conditions associées, de sorte que les modifications similaires corrigent toutes ces conditions. Dans AnalyzeNode, effectuez les changements suivants :

  • Votre analyse sémantique a examiné une seule déclaration de variable. Ce code doit se trouver dans une foreach boucle qui examine toutes les variables déclarées dans la même instruction.
  • Chaque variable déclarée doit avoir un initialiseur.
  • L’initialiseur de chaque variable déclarée doit être une constante au moment de la compilation.

Dans votre AnalyzeNode méthode, remplacez l’analyse sémantique d’origine :

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
VariableDeclaratorSyntax variable = localDeclaration.Declaration.Variables.Single();
ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
    return;
}

avec l’extrait de code suivant :

// Ensure that all variables in the local declaration have initializers that
// are assigned with constant values.
foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables)
{
    EqualsValueClauseSyntax initializer = variable.Initializer;
    if (initializer == null)
    {
        return;
    }

    Optional<object> constantValue = context.SemanticModel.GetConstantValue(initializer.Value, context.CancellationToken);
    if (!constantValue.HasValue)
    {
        return;
    }
}

// Perform data flow analysis on the local declaration.
DataFlowAnalysis dataFlowAnalysis = context.SemanticModel.AnalyzeDataFlow(localDeclaration);

foreach (VariableDeclaratorSyntax variable in localDeclaration.Declaration.Variables)
{
    // Retrieve the local symbol for each variable in the local declaration
    // and ensure that it is not written outside of the data flow analysis region.
    ISymbol variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable, context.CancellationToken);
    if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
    {
        return;
    }
}

La première foreach boucle examine chaque déclaration de variable à l’aide de l’analyse syntaxique. La première vérification garantit que la variable a un initialiseur. La deuxième vérification garantit que l’initialiseur est une constante. La seconde boucle contient l'analyse sémantique originale. Les vérifications sémantiques se trouvent dans une boucle distincte, car elles ont un impact plus important sur les performances. Réexécutez vos tests, et vous devriez les voir tous réussir.

Ajouter le vernis final

Vous avez presque terminé. Il existe quelques conditions supplémentaires que votre analyseur doit gérer. Visual Studio appelle des analyseurs pendant que l’utilisateur écrit du code. Il arrive souvent que votre analyseur soit appelé pour du code qui ne compile pas. La méthode de l'analyseur diagnostique AnalyzeNode ne vérifie pas si la valeur constante est convertible au type de variable. Ainsi, l’implémentation actuelle convertit aisément une déclaration incorrecte telle que int i = "abc" vers une constante locale. Ajoutez une méthode de test pour ce cas :

        [TestMethod]
        public async Task DeclarationIsInvalid_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        int x = {|CS0029:""abc""|};
    }
}
");
        }

En outre, les types de référence ne sont pas gérés correctement. La seule valeur constante autorisée pour un type référence est null, sauf dans le cas de System.String, qui autorise les littéraux de chaîne. En d’autres termes, const string s = "abc" c’est légal, mais const object s = "abc" ce n’est pas le cas. Cet extrait de code vérifie cette condition :

        [TestMethod]
        public async Task DeclarationIsNotString_NoDiagnostic()
        {
            await VerifyCS.VerifyAnalyzerAsync(@"
using System;

class Program
{
    static void Main()
    {
        object s = ""abc"";
    }
}
");
        }

Pour être exhaustif, vous devez ajouter un autre test afin de vous assurer que vous pouvez créer une déclaration de constante pour une chaîne. L’extrait de code suivant définit à la fois le code qui déclenche le diagnostic et le code après l’application du correctif :

        [TestMethod]
        public async Task StringCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|string s = ""abc"";|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const string s = ""abc"";
    }
}
");
        }

Enfin, si une variable est déclarée avec le var mot clé, le correctif de code fait la mauvaise chose et génère une const var déclaration, qui n’est pas prise en charge par le langage C#. Pour résoudre ce bogue, le correctif de code doit remplacer le var mot clé par le nom du type déduit :

        [TestMethod]
        public async Task VarIntDeclarationCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|var item = 4;|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const int item = 4;
    }
}
");
        }

        [TestMethod]
        public async Task VarStringDeclarationCouldBeConstant_Diagnostic()
        {
            await VerifyCS.VerifyCodeFixAsync(@"
using System;

class Program
{
    static void Main()
    {
        [|var item = ""abc"";|]
    }
}
", @"
using System;

class Program
{
    static void Main()
    {
        const string item = ""abc"";
    }
}
");
        }

Heureusement, tous les bogues ci-dessus peuvent être résolus à l’aide des mêmes techniques que celles que vous venez d’apprendre.

Pour corriger le premier bogue, ouvrez d’abord MakeConstAnalyzer.cs et recherchez la boucle foreach où chacun des initialiseurs de la déclaration locale est vérifié pour s’assurer qu’ils sont affectés avec des valeurs constantes. Juste avant la première boucle foreach, appelez context.SemanticModel.GetTypeInfo() pour récupérer des informations détaillées sur le type déclaré de la déclaration locale :

TypeSyntax variableTypeName = localDeclaration.Declaration.Type;
ITypeSymbol variableType = context.SemanticModel.GetTypeInfo(variableTypeName, context.CancellationToken).ConvertedType;

Ensuite, dans votre boucle foreach, vérifiez chaque initialiseur pour vous assurer qu’il peut être converti dans le type de variable. Ajoutez la vérification suivante après avoir vérifié que l’initialiseur est une constante :

// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
Conversion conversion = context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
    return;
}

Le changement suivant s’appuie sur le dernier. Avant l’accolade fermante de la première boucle foreach, ajoutez le code suivant pour vérifier le type de la déclaration locale si la constante est une chaîne ou une valeur null.

// Special cases:
//  * If the constant value is a string, the type of the local declaration
//    must be System.String.
//  * If the constant value is null, the type of the local declaration must
//    be a reference type.
if (constantValue.Value is string)
{
    if (variableType.SpecialType != SpecialType.System_String)
    {
        return;
    }
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
    return;
}

Vous devez écrire un peu plus de code dans votre fournisseur de correctifs de code pour remplacer le var mot clé par le nom de type correct. Revenez à MakeConstCodeFixProvider.cs. Le code que vous allez ajouter effectue les étapes suivantes :

  • Vérifiez si la déclaration est une var déclaration et si c’est le cas :
  • Créez un nouveau type pour le type déduit.
  • Assurez-vous que la déclaration de type n’est pas un alias. Si c’est le cas, il est légal de déclarer const var.
  • Assurez-vous que var ne soit pas un nom de type dans ce programme. (Si c’est le cas, const var est légal).
  • Simplifier le nom de type complet

Cela ressemble à beaucoup de code. Ce n’est pas le cas. Remplacez la ligne qui déclare et initialise newLocal par le code suivant. Cela se produit immédiatement après l’initialisation de newModifiers.

// If the type of the declaration is 'var', create a new type name
// for the inferred type.
VariableDeclarationSyntax variableDeclaration = localDeclaration.Declaration;
TypeSyntax variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
    SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

    // Special case: Ensure that 'var' isn't actually an alias to another type
    // (e.g. using var = System.String).
    IAliasSymbol aliasInfo = semanticModel.GetAliasInfo(variableTypeName, cancellationToken);
    if (aliasInfo == null)
    {
        // Retrieve the type inferred for var.
        ITypeSymbol type = semanticModel.GetTypeInfo(variableTypeName, cancellationToken).ConvertedType;

        // Special case: Ensure that 'var' isn't actually a type named 'var'.
        if (type.Name != "var")
        {
            // Create a new TypeSyntax for the inferred type. Be careful
            // to keep any leading and trailing trivia from the var keyword.
            TypeSyntax typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
                .WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
                .WithTrailingTrivia(variableTypeName.GetTrailingTrivia());

            // Add an annotation to simplify the type name.
            TypeSyntax simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation);

            // Replace the type in the variable declaration.
            variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
        }
    }
}
// Produce the new local declaration.
LocalDeclarationStatementSyntax newLocal = trimmedLocal.WithModifiers(newModifiers)
                           .WithDeclaration(variableDeclaration);

Vous devez ajouter une using directive pour utiliser le Simplifier type :

using Microsoft.CodeAnalysis.Simplification;

Exécutez vos tests, et ils doivent tous réussir. Félicitez-vous en exécutant votre analyseur terminé. Appuyez sur Ctrl+F5 pour exécuter le projet d’analyseur dans une deuxième instance de Visual Studio avec l’extension Roslyn Preview chargée.

  • Dans la deuxième instance de Visual Studio, créez un projet d’application console C# et ajoutez-y int x = "abc"; la méthode Main. Grâce au premier correctif de bogue, aucun avertissement ne doit être signalé pour cette déclaration de variable locale (bien qu’il existe une erreur du compilateur comme prévu).
  • Ensuite, ajoutez object s = "abc"; à la méthode Main. En raison du deuxième correctif de bogue, aucun avertissement ne doit être signalé.
  • Enfin, ajoutez une autre variable locale qui utilise le var mot clé. Vous verrez qu’un avertissement est signalé et qu’une suggestion apparaît en dessous à gauche.
  • Déplacez le signe d’insertion de l’éditeur sur le trait de soulignement ondulé et appuyez sur Ctrl+.. pour afficher le correctif de code suggéré. Lors de la sélection de votre correctif de code, notez que le var mot clé est maintenant géré correctement.

Enfin, ajoutez le code suivant :

int i = 2;
int j = 32;
int k = i + j;

Après ces modifications, vous voyez des soulignements rouges uniquement sur les deux premières variables. Ajoutez const à i et j, et vous obtenez un nouvel avertissement sur k car il peut maintenant être const.

Félicitations! Vous avez créé votre première extension de la plateforme du compilateur .NET qui effectue une analyse du code à la volée pour détecter un problème et fournit un correctif rapide pour le corriger. En cours de route, vous avez appris de nombreuses API de code qui font partie du Kit de développement logiciel (SDK) de la plateforme de compilateur .NET (API Roslyn). Vous pouvez vérifier votre travail par rapport à l’exemple terminé dans notre dépôt GitHub d’exemples.

Autres ressources