Freigeben über


Tutorial: Schreiben Ihres ersten Analysetools und Codefixes

Das .NET Compiler Platform SDK stellt die Tools bereit, die Sie zum Erstellen von benutzerdefinierten Diagnose-Tools (Analyser), Codefixes, Code-Refaktorierung und Diagnoseunterdrücker für C#- oder Visual Basic-Code benötigen. Ein Analyzer enthält Code, der Verstöße gegen Ihre Regel erkennt. Ihr Code-Fix enthält den Code, der die Verletzung behebt. Die von Ihnen implementierten Regeln können alles von Codestruktur bis Codierungsstil bis hin zu Benennungskonventionen und mehr sein. Die .NET Compiler Platform stellt das Framework zum Ausführen der Analyse bereit, während die Entwickler Code schreiben, mit allen Features der Visual Studio-Benutzeroberfläche für das Beheben von Codeproblemen: Anzeigen von Wellenlinien im Editor, Auffüllen der Fehlerliste von Visual Studio, Erstellen der "Glühbirnen"-Vorschläge und Darstellung der umfassenden Vorschau vorgeschlagener Fixe.

In diesem Lernprogramm werden Sie die Erstellung eines Analyzers und eine begleitende Codekorrektur mithilfe der Roslyn-APIs erkunden. Ein Analyzer ist eine Möglichkeit, Quellcodeanalyse durchzuführen und dem Benutzer ein Problem zu melden. Optional kann eine Codekorrektur mit dem Analyzer verknüpft werden, um eine Änderung des Quellcodes des Benutzers darzustellen. In diesem Tutorial wird ein Analysetool erstellt, das Deklarationen von lokalen Variablen enthält, die mithilfe des const-Modifizierers deklariert werden könnten, es aber nicht sind. Die zugehörige Codekorrektur ändert diese Deklarationen, um den const Modifier hinzuzufügen.

Voraussetzungen

Sie müssen das .NET Compiler Platform SDK über das Visual Studio Installer installieren:

Installationsanweisungen – Visual Studio Installer

Es gibt zwei verschiedene Möglichkeiten, das .NET Compiler Platform SDK im Visual Studio Installer zu finden:

Installieren mit dem Visual Studio Installer – Arbeitslastenansicht

Das .NET Compiler Platform SDK wird nicht automatisch als Teil der Visual Studio-Erweiterungsentwicklungslast ausgewählt. Sie müssen sie als optionale Komponente auswählen.

  1. Ausführen des Visual Studio-Installationsprogramms
  2. Wählen Sie Ändern aus.
  3. Überprüfen Sie die Arbeitsauslastung der Visual Studio-Erweiterungsentwicklung .
  4. Öffnen Sie den Knoten Visual Studio-Extensionentwicklung in der Zusammenfassungsstruktur.
  5. Aktivieren Sie das Kontrollkästchen für .NET Compiler Platform SDK. Sie finden sie unter den optionalen Komponenten zuletzt.

Optional möchten Sie auch, dass der DGML-Editor Diagramme in der Visualisierung anzeigt:

  1. Öffnen Sie den Knoten „Einzelne Komponenten“ im Zusammenfassungsbaum.
  2. Aktivieren Sie das Kontrollkästchen für den DGML-Editor.

Installieren mit dem Visual Studio Installer – Registerkarte "Einzelne Komponenten"

  1. Ausführen des Visual Studio-Installationsprogramms
  2. Wählen Sie Ändern aus.
  3. Registerkarte "Einzelne Komponenten " auswählen
  4. Aktivieren Sie das Kontrollkästchen für .NET Compiler Platform SDK. Sie finden sie oben im Abschnitt "Compiler", "Buildtools" und "Laufzeiten" .

Optional möchten Sie auch, dass der DGML-Editor Diagramme in der Visualisierung anzeigt:

  1. Aktivieren Sie das Kontrollkästchen für den DGML-Editor. Sie finden sie im Abschnitt "Codetools ".

Es gibt mehrere Schritte zum Erstellen und Überprüfen des Analyzers:

  1. Erstellen Sie die Lösung.
  2. Registrieren Sie den Namen und die Beschreibung des Analyzers.
  3. Berichten Sie über Warnungen und Empfehlungen des Analyzers.
  4. Implementieren Sie die Codekorrektur, um Empfehlungen zu akzeptieren.
  5. Verbessern Sie die Analyse durch Komponententests.

Erstellen der Lösung

  • Wählen Sie in Visual Studio „Datei“ > „Neu“ > „Projekt...“ aus, um das Dialogfeld „Neues Projekt“ anzuzeigen.
  • Wählen Sie unter Visual C# > -Erweiterbarkeit die Option Analyzer mit Codekorrektur (.NET Standard) aus.
  • Benennen Sie Ihr Projekt "MakeConst", und klicken Sie auf "OK".

Hinweis

Möglicherweise wird ein Kompilierungsfehler angezeigt (MSB4062: Die Aufgabe "CompareBuildTaskVersion" konnte nicht geladen werden". Um dies zu beheben, aktualisieren Sie die NuGet-Pakete in der Projektmappe mit dem NuGet-Paket-Manager, oder verwenden Sie Update-Package im Paket-Manager-Konsolenfenster.

Erkunden Sie die Analysevorlage

Die Analyse mit Codekorrekturvorlage erstellt fünf Projekte:

  • MakeConst, das den Analyzer enthält.
  • MakeConst.CodeFixes, das die Codekorrektur enthält.
  • MakeConst.Package, das verwendet wird, um NuGet-Paket für die Analyse und Codekorrektur zu erstellen.
  • MakeConst.Test, bei dem es sich um ein Komponententestprojekt handelt.
  • MakeConst.Vsix, das das Standardstartprojekt ist, das eine zweite Instanz von Visual Studio startet, die den neuen Analyzer geladen hat. Drücken Sie F5 , um das VSIX-Projekt zu starten.

Hinweis

Analyzer sollten auf .NET Standard 2.0 abzielen, da sie in .NET Core-Umgebung (Befehlszeilenbuilds) und .NET Framework-Umgebung (Visual Studio) ausgeführt werden können.

Tipp

Wenn Sie die Analyse ausführen, starten Sie eine zweite Kopie von Visual Studio. Diese zweite Kopie verwendet eine andere Registrierungsstruktur zum Speichern von Einstellungen. Auf diese Weise können Sie die visuellen Einstellungen in den beiden Kopien von Visual Studio unterscheiden. Sie können ein anderes Design für die experimentelle Ausführung von Visual Studio auswählen. Achten Sie außerdem darauf, Ihre Einstellungen oder Ihre Anmeldung nicht mithilfe der Testinstanz von Visual Studio als mobiler Benutzer auf Ihr Visual Studio-Konto zu übertragen. Dadurch bleiben die Einstellungen unterschiedlich.

Die Struktur enthält nicht nur das Analysetool in der Entwicklung, sondern auch alle vorherigen geöffneten Analysetools. Um den Roslyn-Hive zurückzusetzen, müssen Sie ihn manuell aus %LocalAppData%\Microsoft\VisualStudio löschen. Der Ordnername der Roslyn-Struktur endet auf Roslyn, z. B. 16.0_9ae182f9Roslyn. Beachten Sie, dass Sie die Projektmappe möglicherweise bereinigen und nach dem Löschen der Struktur neu erstellen müssen.

Erstellen Sie in der zweiten Visual Studio-Instanz, die Sie gerade gestartet haben, ein neues C#-Konsolenanwendungsprojekt (jedes Zielframework funktioniert – Analysegeräte funktionieren auf Quellebene.) Zeigen Sie mit der Maus auf das Token mit einer wellenförmigen Unterstreichung, und der von einem Analyzer bereitgestellte Warntext wird angezeigt.

Die Vorlage erstellt einen Analyzer, der eine Warnung für jede Typdeklaration meldet, in der der Typname Kleinbuchstaben enthält, wie in der folgenden Abbildung dargestellt:

Analysetool beim Melden einer Warnung

Die Vorlage stellt darüber hinaus einen Codefix bereit, der jeden Typnamen, der Kleinbuchstaben enthält, in durchgängig Großbuchstaben ändert. Sie können auf die Glühbirne klicken, die mit der Warnung angezeigt wird, um die vorgeschlagenen Änderungen anzuzeigen. Wenn Sie die vorgeschlagenen Änderungen akzeptieren, werden der Typname und alle Verweise auf diesen Typ in der Lösung aktualisiert. Nachdem Sie die erste Analyse in Aktion gesehen haben, schließen Sie die zweite Visual Studio-Instanz, und kehren Sie zum Analyseprojekt zurück.

Sie müssen keine zweite Kopie von Visual Studio starten und neuen Code schreiben, um jede Änderung in Ihrem Analyzer zu testen. Die Vorlage erstellt auch ein Komponententestprojekt für Sie. Dieses Projekt enthält zwei Tests. TestMethod1 zeigt das typische Format eines Tests an, der Code analysiert, ohne eine Diagnose auszulösen. TestMethod2 zeigt das Format eines Tests an, der eine Diagnose auslöst, und wendet dann eine vorgeschlagene Codekorrektur an. Beim Erstellen der Analyse und Codekorrektur schreiben Sie Tests für verschiedene Codestrukturen, um Ihre Arbeit zu überprüfen. Komponententests für Analysegeräte sind viel schneller als interaktiv mit Visual Studio zu testen.

Tipp

Analysetool-Komponententests sind ein hervorragendes Werkzeug, wenn Sie wissen, welche Codekonstrukte Ihr Analysetool auslösen sollten und welche nicht. Das Laden Ihres Analyzers in einer anderen Kopie von Visual Studio ist ein hervorragendes Werkzeug, um Konstrukte zu erkunden und zu finden, die Ihnen möglicherweise noch nicht eingefallen sind.

In diesem Lernprogramm schreiben Sie einen Analyzer, der dem Benutzer alle lokalen Variablendeklarationen meldet, die in lokale Konstanten konvertiert werden können. Betrachten Sie z. B. den folgenden Code:

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

Im obigen x Code wird ein Konstantenwert zugewiesen und nie geändert. Sie kann mithilfe des const Modifizierers deklariert werden:

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

Dies bringt die Analyse mit sich, mit der bestimmt wird, ob eine Variable zu einer Konstanten gemacht werden kann, wozu Syntaxanalyse, Konstantenanalyse des Initialisiererausdrucks und eine Datenflussanalyse erforderlich sind, um sicherzustellen, dass zu keinem Zeitpunkt in die Variable geschrieben wird. Die .NET-Compilerplattform stellt APIs bereit, die die Durchführung dieser Analyse vereinfachen.

Analyzerregistrierungen erstellen

Die Vorlage erstellt die ursprüngliche DiagnosticAnalyzer Klasse in der MakeConstAnalyzer.cs Datei. Dieser anfängliche Analyzer zeigt zwei wichtige Eigenschaften jedes Analyzers an.

  • Jede Diagnoseanalyse muss ein [DiagnosticAnalyzer] Attribut bereitstellen, das die von ihr ausgeführte Sprache beschreibt.
  • Jeder Diagnoseanalysator muss (direkt oder indirekt) von der DiagnosticAnalyzer Klasse abgeleitet werden.

Die Vorlage zeigt auch die grundlegenden Features, die Teil eines Analysegeräts sind:

  1. Registrieren von Aktionen. Die Aktionen stellen Codeänderungen dar, die ihren Analyzer auslösen sollten, um Code auf Verstöße zu untersuchen. Wenn Visual Studio Codebearbeitungen erkennt, die einer registrierten Aktion entsprechen, ruft sie die registrierte Methode des Analyzers auf.
  2. Erstellen Sie Diagnose. Wenn die Analyse einen Verstoß erkennt, wird ein Diagnoseobjekt erstellt, das Visual Studio verwendet, um den Benutzer über die Verletzung zu benachrichtigen.

Sie registrieren Aktionen in Ihrer Überschreibung der DiagnosticAnalyzer.Initialize(AnalysisContext)-Methode. In diesem Lernprogramm besuchen Sie Syntaxknoten , die nach lokalen Deklarationen suchen, und sehen, welche dieser Konstantenwerte aufweisen. Wenn eine Deklaration konstant sein könnte, erstellt und meldet der Analyzer eine Diagnose.

Der erste Schritt besteht darin, die Registrierungskonstanten und Initialize -methoden zu aktualisieren, damit diese Konstanten ihren "Make Const"-Analyzer angeben. Die meisten Zeichenfolgenkonstanten werden in der Zeichenfolgenressourcendatei definiert. Sie sollten diese Vorgehensweise für eine einfachere Lokalisierung befolgen. Öffnen Sie die Datei Resources.resx für das MakeConst Analyzer-Projekt. Dadurch wird der Ressourcen-Editor angezeigt. Aktualisieren Sie die Zeichenfolgenressourcen wie folgt:

  • Ändern Sie AnalyzerDescription in "Variables that are not modified should be made constants.".
  • Ändern Sie AnalyzerMessageFormat in "Variable '{0}' can be made constant".
  • Ändern Sie AnalyzerTitle in "Variable can be made constant".

Wenn Sie fertig sind, sollte der Ressourcen-Editor wie in der folgenden Abbildung dargestellt angezeigt werden:

Aktualisieren von Zeichenfolgenressourcen

Die verbleibenden Änderungen befinden sich in der Analysedatei. Öffnen Sie MakeConstAnalyzer.cs in Visual Studio. Ändern Sie die registrierte Aktion von einer Aktion, die auf Symbole wirkt, in eine, die auf die Syntax wirkt. Suchen Sie in der MakeConstAnalyzerAnalyzer.Initialize Methode die Zeile, in der die Aktion für Symbole registriert wird.

context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);

Ersetzen Sie sie durch die folgende Zeile:

context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);

Nach dieser Änderung können Sie die AnalyzeSymbol Methode löschen. Dieses Analysetool untersucht SyntaxKind.LocalDeclarationStatement-Anweisungen, keine SymbolKind.NamedType-Anweisungen. Beachten Sie, dass AnalyzeNode mit roten Wellenlinien unterstrichen ist. Der Code, den Sie soeben hinzugefügt haben, verweist auf eine AnalyzeNode Methode, die nicht deklariert wurde. Deklarieren Sie diese Methode mithilfe des folgenden Codes:

private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
}

Ändern Sie Category zu "Usage" in MakeConstAnalyzer.cs, wie im folgenden Code gezeigt.

private const string Category = "Usage";

Suchen nach lokalen Deklarationen, die möglicherweise konstant sein könnten

Es ist an der Zeit, die erste Version der AnalyzeNode Methode zu schreiben. Es sollte nach einer einzelnen lokalen Deklaration suchen, die möglicherweise const sein könnte, es aber nicht ist, wie im folgenden Code:

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

Der erste Schritt besteht darin, lokale Deklarationen zu finden. Fügen Sie den folgenden Code AnalyzeNode in MakeConstAnalyzer.cs hinzu:

var localDeclaration = (LocalDeclarationStatementSyntax)context.Node;

Diese Umwandlung funktioniert immer, da Ihr Analysetool für Änderungen an lokalen Deklarationen und nur an lokalen Deklarationen registriert wurde. Kein anderer Knotentyp löst einen Aufruf Ihrer Methode AnalyzeNode aus. Überprüfen Sie als Nächstes die Deklaration für alle const-Modifizierer. Wenn Sie sie finden, kehren Sie sofort zurück. Der folgende Code sucht nach const Modifizierern in der lokalen Deklaration:

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

Schließlich müssen Sie überprüfen, ob die Variable sein constkönnte. Das bedeutet sicherzustellen, dass es nach der Initialisierung nie erneut zugewiesen wird.

Sie führen einige semantische Analysen mithilfe der SyntaxNodeAnalysisContext. Sie verwenden das context Argument, um zu bestimmen, ob die lokale Variablendeklaration erstellt constwerden kann. A Microsoft.CodeAnalysis.SemanticModel stellt alle semantischen Informationen in einer einzelnen Quelldatei dar. Weitere Informationen finden Sie im Artikel, in dem semantische Modelle behandelt werden. Sie verwenden das Microsoft.CodeAnalysis.SemanticModel zum Durchführen einer Datenflussanalyse für die lokale Deklarationsanweisung. Anschließend verwenden Sie die Ergebnisse dieser Datenflussanalyse, um sicherzustellen, dass die lokale Variable an keiner anderen Stelle mit einem neuen Wert geschrieben wird. Rufen Sie das Erweiterungsmitglied GetDeclaredSymbol auf, um das ILocalSymbol für die Variable abzurufen, und überprüfen Sie, ob es nicht in der DataFlowAnalysis.WrittenOutside Sammlung der Datenflussanalyse enthalten ist. Fügen Sie den folgenden Code am Ende der AnalyzeNode Methode hinzu:

// 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;
}

Der hinzugefügte Code stellt sicher, dass die Variable nicht geändert wird und daher const gesetzt werden kann. Es ist an der Zeit, die Diagnose auszuheben. Fügen Sie den folgenden Code als letzte Zeile hinzu:AnalyzeNode

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

Sie können den Fortschritt überprüfen, indem Sie F5 drücken, um den Analysevorgang auszuführen. Sie können die zuvor erstellte Konsolenanwendung laden und dann den folgenden Testcode hinzufügen:

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

Die Glühbirne sollte angezeigt werden, und Ihr Analysegerät sollte eine Diagnose melden. Je nach Ihrer Version von Visual Studio sehen Sie jedoch:

  • Die Glühbirne, die weiterhin den aus der Vorlage erzeugten Codefix verwendet, weist darauf hin, dass Großschreibung möglich ist.
  • Eine Bannermeldung am oberen Rand des Editors mit dem Hinweis, dass in „MakeConstCodeFixProvider“ ein Fehler aufgetreten ist und deaktiviert wurde. Dies liegt daran, dass der Codekorrekturanbieter noch nicht angepasst wurde und immer noch erwartet, TypeDeclarationSyntax-Elemente anstelle von LocalDeclarationStatementSyntax-Elementen zu finden.

Im nächsten Abschnitt wird erläutert, wie Sie die Codekorrektur schreiben.

Code-Fix schreiben

Ein Analyzer kann eine oder mehrere Codefixes bereitstellen. Eine Codekorrektur definiert eine Bearbeitung, die das gemeldete Problem behebt. Für die von Ihnen erstellte Analyse können Sie eine Codekorrektur bereitstellen, die das Schlüsselwort const einfügt:

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

Der Benutzer wählt ihn aus der Benutzeroberfläche der Glühbirne im Editor aus, und Visual Studio ändert den Code.

Öffnen Sie die Datei CodeFixResources.resx , und wechseln Sie CodeFixTitle zu "Make constant".

Öffnen Sie die MakeConstCodeFixProvider.cs Datei, die von der Vorlage hinzugefügt wurde. Dieser Codefix ist bereits mit der Diagnose-ID verschaltet, die von Ihrem Diagnoseanalysetool erzeugt wird, er implementiert jedoch noch nicht die gewünschte Codetransformation.

Löschen Sie als Nächstes die MakeUppercaseAsync Methode. Es gilt nicht mehr.

Alle Codefix-Anbieter leiten sich von CodeFixProvider ab. Sie alle überschreiben CodeFixProvider.RegisterCodeFixesAsync(CodeFixContext), um verfügbare Codefixe zu melden. Ändern Sie in RegisterCodeFixesAsync den Typ des Vorfahrenknotens, nach dem Sie suchen, zu einem LocalDeclarationStatementSyntax, um der Diagnose zu entsprechen.

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

Ändern Sie als Nächstes die letzte Zeile, um eine Codekorrektur zu registrieren. Ihr Fix erstellt ein neues Dokument, das aus dem Hinzufügen des const Modifizierers zu einer vorhandenen Deklaration resultiert:

// 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);

Sie werden rote Wellenlinien in dem soeben hinzugefügten Code unter dem Symbol MakeConstAsync bemerken. Fügen Sie eine Deklaration für MakeConstAsync wie im folgenden Code hinzu:

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

Die neue MakeConstAsync Methode transformiert die Document Quelldatei des Benutzers in eine neue Document , die jetzt eine const Deklaration enthält.

Sie erstellen ein neues const Schlüsselwort-Token, das am Anfang des Deklarationsstatements eingefügt wird. Achten Sie darauf, zuerst alle führenden Trivia aus dem ersten Token der Deklarationsanweisung zu entfernen und sie an das const-Token anzufügen. Fügen Sie der MakeConstAsync-Methode folgenden Code hinzu:

// 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));

Fügen Sie als Nächstes das const Token mithilfe des folgenden Codes zur Deklaration hinzu:

// 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);

Als Nächstes formatieren Sie die neue Deklaration so, dass sie den C#-Formatierungsregeln entspricht. Durch das Formatieren Ihrer Änderungen gemäß bestehendem Code schaffen Sie ein besseres Anwendererlebnis. Fügen Sie die folgende Anweisung unmittelbar nach dem vorhandenen Code hinzu:

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

Für diesen Code ist ein neuer Namespace erforderlich. Fügen Sie die folgende using Direktive am Anfang der Datei hinzu:

using Microsoft.CodeAnalysis.Formatting;

Der letzte Schritt besteht darin, Ihre Bearbeitung vorzunehmen. Für diesen Prozess gibt es drei Schritte:

  1. Abrufen eines Handles zu dem vorhandenen Dokument.
  2. Erstellen Sie ein neues Dokument, indem Sie die vorhandene Deklaration durch die neue Deklaration ersetzen.
  3. Gibt das neue Dokument zurück.

Fügen Sie den folgenden Code am Ende der MakeConstAsync Methode hinzu:

// 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);

Ihre Codekorrektur steht bereit zum Ausprobieren. Drücken Sie F5 , um das Analyseprojekt in einer zweiten Instanz von Visual Studio auszuführen. Erstellen Sie in der zweiten Visual Studio-Instanz ein neues C#-Konsolenanwendungsprojekt, und fügen Sie der Main-Methode einige lokale Variablendeklarationen hinzu, die mit Konstantenwerten initialisiert wurden. Sie werden sehen, dass sie als Warnungen gemeldet werden, wie unten zu sehen ist.

Kann const-Warnungen erzeugen

Sie haben viel Fortschritte gemacht. Unter den Deklarationen, die in const umgewandelt werden können, werden Wellenlinien angezeigt. Aber es gibt noch Arbeit. Alles hier funktioniert, wenn Sie const den Deklarationen beginnend bei i, dann weiter mit j und schließlich k hinzufügen. Wenn Sie den const-Modifizierer aber in einer anderen Reihenfolge zu i zuweisen, beginnend mit k, erzeugt Ihr Analysetool Fehler: k kann nicht const deklariert werden, sofern nicht i und j beide bereits const sind. Sie müssen weitere Analysen durchführen, um sicherzustellen, dass Sie die verschiedenen Möglichkeiten zum Deklarieren und Initialisieren von Variablen korrekt handhaben.

Unit-Tests erstellen

Ihr Analysetool und der Codefix funktionieren beim einfachen Fall einer einzelnen Deklaration, die als „const“ deklariert werden kann. Es gibt zahlreiche mögliche Deklarationsanweisungen, in denen diese Implementierung Fehler macht. Sie tragen diesen Fällen Rechnung, indem Sie mit der Komponententestbibliothek arbeiten, die von der Vorlage erstellt wurde. Es ist viel schneller, als wiederholt eine zweite Kopie von Visual Studio zu öffnen.

Öffnen Sie die MakeConstUnitTests.cs Datei im Komponententestprojekt. Die Vorlage hat zwei Tests erstellt, die den zwei allgemeinen Mustern für einen Komponententest für Analysetools und Codefixe folgen. TestMethod1 zeigt das Muster für einen Test an, der sicherstellt, dass der Analyzer keine Diagnose meldet, wenn dies nicht der Grund ist. TestMethod2 zeigt das Muster zum Melden einer Diagnose und Ausführen der Codekorrektur an.

Die Vorlage verwendet Microsoft.CodeAnalysis.Testing-Pakete für Komponententests.

Tipp

Die Testbibliothek unterstützt eine spezielle Markupsyntax, einschließlich der folgenden:

  • [|text|]: gibt an, dass eine Diagnose gemeldet wird für text. Dieses Formular kann standardmäßig nur für Tests von Analyzern verwendet werden, die genau ein DiagnosticDescriptor enthalten, das von DiagnosticAnalyzer.SupportedDiagnostics bereitgestellt wird.
  • {|ExpectedDiagnosticId:text|}: gibt an, dass eine Diagnose mit IdExpectedDiagnosticId für text gemeldet wurde.

Ersetzen Sie die Vorlagentests in der MakeConstUnitTest Klasse durch die folgende Testmethode:

        [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);
    }
}
");
        }

Führen Sie diesen Test aus, um sicherzustellen, dass er erfolgreich ist. Öffnen Sie in Visual Studio den Test-Explorer, indem Sie ">>" auswählen. Wählen Sie dann "Alle ausführen" aus.

Erstellen von Tests für gültige Deklarationen

Ganz allgemein sollten Analysetool so schnell wie möglich beendet werden und nur minimale Arbeit verrichten. Visual Studio ruft registrierte Analyzer auf, während der Benutzer Code bearbeitet. Reaktionsfähigkeit ist eine wichtige Voraussetzung. Es gibt mehrere Testfälle für Code, die Ihre Diagnose nicht auslösen sollten. Ihr Analysegerät verarbeitet bereits mehrere dieser Tests. Fügen Sie die folgenden Testmethoden hinzu, um diese Fälle darzustellen:

        [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);
    }
}
");
        }

Diese Tests werden bestanden, da Ihr Analyseprogramm diese Bedingungen bereits verarbeitet.

  • Variablen, die nach der Initialisierung zugewiesen wurden, werden von der Datenflussanalyse erkannt.
  • Deklarationen, die bereits const sind, werden herausgefiltert, indem auf das Schlüsselwort const Schlüsselword.
  • Deklarationen ohne Initialisierung werden von der Datenanalyse behandelt, die Zuordnungen außerhalb der Deklaration erkennt.

Fügen Sie als Nächstes Testmethoden für Bedingungen hinzu, die Sie noch nicht behandelt haben:

  • Deklarationen, bei denen der Initialisierer keine Konstante ist, da sie keine Kompilierungszeitkonstanten sein können:

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

Es kann noch komplizierter sein, da C# mehrere Deklarationen als eine Anweisung zulässt. Berücksichtigen Sie die folgende Testfall-Zeichenfolgenkonstante:

        [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);
    }
}
");
        }

Die Variable i kann konstant gemacht werden, aber die Variable j kann nicht. Daher kann aus dieser Anweisung keine const-Deklaration gemacht werden.

Führen Sie Ihre Tests erneut aus, und diese beiden letzten Testfälle schlagen fehl.

Aktualisieren Sie Ihren Analyzer, um korrekte Deklarationen zu ignorieren

Sie benötigen einige Verbesserungen der AnalyzeNode Analysemethode, um Code herauszufiltern, der diesen Bedingungen entspricht. Sie sind alle zugehörigen Bedingungen, sodass ähnliche Änderungen alle diese Bedingungen beheben. Nehmen Sie an AnalyzeNode die folgenden Änderungen vor:

  • Ihre semantische Analyse untersuchte eine einzelne Variablendeklaration. Dieser Code muss sich in einer foreach Schleife befinden, die alle variablen untersucht, die in derselben Anweisung deklariert sind.
  • Jede deklarierte Variable muss über einen Initialisierer verfügen.
  • Jeder deklarierten Variableninitialisierer muss eine Kompilierungszeitkonstante sein.

Ersetzen Sie in Ihrer AnalyzeNode Methode die ursprüngliche semantische Analyse:

// 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;
}

mit dem folgenden Codeausschnitt:

// 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;
    }
}

Die erste foreach Schleife untersucht jede Variablendeklaration mithilfe der syntaktischen Analyse. Die erste Überprüfung garantiert, dass die Variable über einen Initialisierer verfügt. Die zweite Überprüfung garantiert, dass der Initialisierer eine Konstante ist. Die zweite Schleife weist die ursprüngliche semantische Analyse auf. Die semantischen Prüfungen befinden sich in einer separaten Schleife, da sie eine größere Auswirkung auf die Leistung hat. Führen Sie Ihre Tests erneut aus, und Sie sollten sehen, dass sie alle erfolgreich sind.

Abschließender Feinschliff hinzufügen

Sie haben es fast geschafft! Es gibt noch ein paar weitere Bedingungen, mit denen Ihr Analysetool umgehen muss. Visual Studio ruft Analyseanalysen auf, während der Benutzer Code schreibt. Es ist häufig der Fall, dass Ihr Analyzer für Code aufgerufen wird, der nicht kompiliert wird. Die Methode des AnalyzeNode Diagnoseanalyses überprüft nicht, ob der Konstantenwert in den Variablentyp konvertierbar ist. Daher wird die aktuelle Implementierung eine falsche Deklaration wie int i = "abc" problemlos in eine lokale Konstante umwandeln. Fügen Sie für diesen Fall eine Testmethode hinzu:

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

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

Darüber hinaus werden Verweistypen nicht ordnungsgemäß behandelt. Der einzige für einen Bezugstyp zulässige Konstantenwert ist null, außer im Fall von System.String, der Zeichenfolgenliterale zulässt. Mit anderen Worten, const string s = "abc" ist legal, aber const object s = "abc" nicht. Dieser Codeausschnitt überprüft diese Bedingung:

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

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

Um gründlich zu sein, müssen Sie einen weiteren Test hinzufügen, um sicherzustellen, dass Sie eine Konstantendeklaration für eine Zeichenfolge erstellen können. Der folgende Codeausschnitt definiert sowohl den Code, der die Diagnose auslöst, als auch den Code, nachdem der Fix angewendet wurde:

        [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"";
    }
}
");
        }

Wenn eine Variable mit dem var-Schlüsselwort deklariert wird, führt die Korrekturmaßnahme eine falsche Aktion aus und generiert eine const var-Deklaration, die von der C#-Sprache nicht unterstützt wird. Um diesen Fehler zu beheben, muss die Codekorrektur das var Schlüsselwort durch den Namen des abgeleiteten Typs ersetzen:

        [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"";
    }
}
");
        }

Glücklicherweise können alle oben genannten Fehler mit den gleichen Techniken behoben werden, die Sie gerade gelernt haben.

Um den ersten Fehler zu beheben, öffnen Sie zuerst MakeConstAnalyzer.cs , und suchen Sie die Foreachschleife, in der jede der Initialisierer der lokalen Deklaration überprüft wird, um sicherzustellen, dass sie mit Konstantenwerten zugewiesen werden. Rufen Sie unmittelbar vor der ersten foreach-Schleife context.SemanticModel.GetTypeInfo() auf, um detaillierte Informationen über den deklarierten Typ der lokalen Deklaration abzurufen.

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

Überprüfen Sie dann innerhalb der foreach Schleife jeden Initialisierer, um sicherzustellen, dass er in den Variablentyp umsetzbar ist. Fügen Sie die folgende Überprüfung hinzu, nachdem Sie sichergestellt haben, dass der Initialisierer eine Konstante ist:

// 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;
}

Die nächste Änderung baut auf der letzten auf. Fügen Sie vor der schließenden geschweiften Klammer der ersten foreach-Schleife den folgenden Code hinzu, um den Typ der lokalen Deklaration zu überprüfen, wenn die Konstante entweder eine Zeichenfolge oder null ist.

// 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;
}

Sie müssen etwas mehr Code in Ihrem Codekorrekturanbieter schreiben, um das var Schlüsselwort durch den richtigen Typnamen zu ersetzen. Kehren Sie zu MakeConstCodeFixProvider.cs zurück. Der code, den Sie hinzufügen, führt die folgenden Schritte aus:

  • Überprüfen Sie, ob die Deklaration eine var Deklaration ist, und wenn ja:
  • Erstellen Sie einen neuen Typ für den abgeleiteten Typ.
  • Stellen Sie sicher, dass die Typdeklaration kein Alias ist. Wenn ja, ist es legal, zu deklarieren const var.
  • Stellen Sie sicher, dass var in diesem Programm kein Typname ist. (Wenn ja, ist const var zulässig.)
  • Vereinfachen des vollständigen Typnamens

Das klingt wie viel Code. Das ist es nicht. Ersetzen Sie die Zeile, die newLocal deklariert und initialisiert, mit dem folgenden Code. Es folgt direkt nach der Initialisierung von 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);

Sie müssen eine using Direktive hinzufügen, um den Simplifier Typ zu verwenden:

using Microsoft.CodeAnalysis.Simplification;

Führen Sie Ihre Tests aus, und sie sollten alle bestehen. Gratulieren Sie sich, indem Sie Ihr fertiges Analysetool ausführen. Drücken Sie STRG+F5 , um das Analyseprojekt in einer zweiten Instanz von Visual Studio auszuführen, wobei die Roslyn Preview-Erweiterung geladen wurde.

  • Erstellen Sie in der zweiten Visual Studio-Instanz ein neues C#-Konsolenanwendungsprojekt, und fügen Sie der Main-Methode hinzu int x = "abc"; . Dank der ersten Fehlerkorrektur sollte keine Warnung für diese lokale Variablendeklaration gemeldet werden (obwohl es einen Compilerfehler wie erwartet gibt).
  • Fügen Sie als Nächstes object s = "abc"; zur Main-Methode hinzu. Aufgrund der zweiten Fehlerkorrektur sollte keine Warnung gemeldet werden.
  • Fügen Sie schließlich eine weitere lokale Variable hinzu, die das var Schlüsselwort verwendet. Sie sehen, dass eine Warnung gemeldet wird und ein Vorschlag unterhalb links angezeigt wird.
  • Bewegen Sie den Editor-Cursor über den verschnörkelten Unterstrich und drücken Sie Ctrl+.. um die vorgeschlagene Codekorrektur anzuzeigen. Beachten Sie beim Auswählen des Codekorrektures, dass das var Schlüsselwort jetzt ordnungsgemäß behandelt wird.

Fügen Sie schließlich den folgenden Code hinzu:

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

Nach diesen Änderungen erhalten Sie rote Wellenlinien nur unter den ersten zwei Variablen. Fügen Sie const sowohl i als auch j hinzu; dadurch erhalten Sie eine neue Warnung bei k, da es jetzt const sein kann.

Glückwunsch! Sie haben Ihre erste .NET Compiler Platform-Erweiterung erstellt, die eine On-the-Fly-Codeanalyse durchführt, um ein Problem zu erkennen und bietet eine schnelle Lösung, um es zu beheben. Auf dem Weg haben Sie viele der Code-APIs kennengelernt, die Teil des .NET Compiler Platform SDK (Roslyn-APIs) sind. Sie können Ihre Arbeit anhand des abgeschlossenen Beispiels in unserem GitHub-Repository für Beispiele überprüfen.

Weitere Ressourcen