Freigeben über


Dieser Artikel wurde maschinell übersetzt.

C#

Hinzufügen von Codefixing zum Roslyn-Analyzer

Alex Turner

Wenn Sie die Schritte in meinem vorherigen Artikel, "Verwendung Roslyn zu schreiben ein Live Code Analyzer für Ihre API" (msdn.microsoft.com/magazine/dn879356), Sie schrieb einen Analysator, der live Fehler bei ungültiger regulärer Ausdruck (Regex) Musterzeichenfolgen angezeigt. Jedes Ungültiges Muster ruft eine rote Wellenlinie im Editor, so wie Sie für Compiler-Fehler sehen, und die Schnörkel live, erscheinen wie Sie Ihren Code eingeben. Dies wird durch die neue Plattform für .NET-Compiler ("Roslyn") APIs, ermöglicht die Vorschau macht die c# und Visual Basic Erfahrungen im Visual Studio 2015 bearbeiten.

Kannst du mehr? Hast du das Domänenwissen zu sehen, nicht nur was falsch ist, sondern auch wie man es beheben, können Sie die entsprechenden Code-Fehlerbehebung durch die neue Visual Studio -Glühbirne vorschlagen. Mit diesem Codefix ermöglicht einen Entwickler mit Ihren Analyzer nicht nur einen Fehler in seinem Code finden – er kann auch sauber machen sofort.

In diesem Artikel, ich werde Ihnen zeigen wie Ihre Diagnose Regex-Analyzer ein Codeanbieters Fix hinzugefügt, die Korrekturen bietet bei jedem Regex-Schnörkel. Das Update wird als ein Element im Menü Glühbirne, sodass der Benutzer Vorschau Kontrollverlust und wenden es auf ihr Code automatisch hinzugefügt werden.

Picking Up, wo Sie aufgehört

Um loszulegen, sicher sein, dass Sie die Schritte im vorhergehenden Artikel durchgeführt haben. In diesem Artikel habe ich Ihnen gezeigt, wie man der erste Hälfte Ihres Analyzer zu schreiben, die die Diagnose Wellenlinien unter jede ungültige Regex Musterzeichenfolge generiert. Dieser Artikel ging Sie durch:

  • Visual Studio 2015 Preview, die SDK und die relevanten Roslyn VSIX-Pakete installiert sind.
  • Erstellen eine neue Diagnose mit Code korrigieren Projekt.
  • Hinzufügen von Code zu DiagnosticAnalyzer.cs der ungültige Regex-Mustererkennung zu implementieren.

Wenn Sie schauen, um schnell aufzuholen, check out Abbildung 1, ­die Listen des endgültigen Codes für DiagnosticAnalyzer.cs.

Abbildung 1 der vollständige Code für DiagnosticAnalyzer.cs

using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace RegexAnalyzer
{
  [DiagnosticAnalyzer(LanguageNames.CSharp)]
  public class RegexAnalyzerAnalyzer : DiagnosticAnalyzer
  {
    public const string DiagnosticId = "Regex";
    internal const string Title = "Regex error parsing string argument";
    internal const string MessageFormat = "Regex error {0}";
    internal const string Category = "Syntax";
    internal static DiagnosticDescriptor Rule =
      new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
      Category, DiagnosticSeverity.Error, isEnabledByDefault: true);
    public override ImmutableArray<DiagnosticDescriptor>
      SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }
    public override void Initialize(AnalysisContext context)
    {
      context.RegisterSyntaxNodeAction(
        AnalyzeNode, SyntaxKind.InvocationExpression);
    }
    private void AnalyzeNode(SyntaxNodeAnalysisContext context)
    {
      var invocationExpr = (InvocationExpressionSyntax)context.Node;
      var memberAccessExpr =
        invocationExpr.Expression as MemberAccessExpressionSyntax;
      if (memberAccessExpr?.Name.ToString() != "Match") return;
      var memberSymbol = context.SemanticModel.
        GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
      if (!memberSymbol?.ToString().StartsWith(
        "System.Text.RegularExpressions.Regex.Match") ?? true) return;
      var argumentList = invocationExpr.ArgumentList as ArgumentListSyntax;
      if ((argumentList?.Arguments.Count ?? 0) < 2) return;
      var regexLiteral =
        argumentList.Arguments[1].Expression as LiteralExpressionSyntax;
      if (regexLiteral == null) return;
      var regexOpt = context.SemanticModel.GetConstantValue(regexLiteral);
      if (!regexOpt.HasValue) return;
      var regex = regexOpt.Value as string;
      if (regex == null) return;
      try
      {
        System.Text.RegularExpressions.Regex.Match("", regex);
      }
      catch (ArgumentException e)
      {
        var diagnostic =
          Diagnostic.Create(Rule, regexLiteral.GetLocation(), e.Message);
        context.ReportDiagnostic(diagnostic);
      }
    }
  }
}

Umwandlung unveränderlich Syntaxbäume

Das letzte Mal, als Sie den Diagnose Analyzer, ungültige Regex Muster zu erkennen schrieb, war der erste Schritt, um die Syntax-Schnellansicht verwenden, um Muster in den Syntaxbäumen zu identifizieren, die Problemcode angegeben. Sie schrieb dann eine Analysemethode, die jedes Mal den jeweiligen Knotentyp lief gefunden wurde. Die Methode überprüft nach dem Muster der Syntax-Knoten, die einen Fehler Wellenlinie gerechtfertigt.

Ein Update zu schreiben, ist ein ähnlicher Prozess. Sie befassen sich in Syntaxbäume, konzentriert sich auf den gewünschten neuen Zustand der Codedateien, nachdem der Benutzer Ihrer ansetzen gilt. Die meisten Code-Fixes beinhalten hinzufügen, entfernen oder Ersetzen von Syntax-Knoten aus der aktuellen Bäume, neue Syntaxbäume zu produzieren. Sie können direkt auf Syntax-Knoten oder benennt verwenden APIs, mit denen Sie z. B. Projektweite Änderungen vornehmen.

Eine sehr wichtige Eigenschaft, über die Syntax Knoten, die Bäume und die Symbole in der .NET-Compiler-Plattform zu verstehen ist, dass sie unveränderlich sind. Sobald ein Syntax-Knoten oder Baum erstellt wurde, nicht geändert werden kann – ein bestimmtes Baum oder Node-Objekt wird immer den gleichen c# oder Visual Basic -Code dar.

Unveränderlichkeit in einer API für die Umwandlung von Source-Code kann counterintuitive scheinen. Wie können Sie hinzufügen, entfernen und ersetzen die untergeordneten Knoten in einen Syntaxbaum, wenn weder die Struktur noch dessen Knoten geändert werden können? Es ist hilfreich, hier um zu prüfen, den .NET String-Typ, einen anderen unveränderlich Sie verwenden wahrscheinlich jeden Tag. Sie Vorgänge zur oft Umwandlung von Zeichenfolgen verketten sie und sogar ersetzen von Teilstrings verwenden String.Replace. Jedoch ändern keiner dieser Vorgänge eigentlich das original String-Objekt. Stattdessen gibt jedem Aufruf ein neues String-Objekt, das den neuen Zustand der Zeichenfolge zurück. Sie können dieses neue Objekt zurück zu Ihrem ursprünglichen Variable zuweisen, aber jede Methode, die, der Sie die Zeichenfolge, die alte übergeben, haben weiterhin den ursprünglichen Wert.

Hinzufügen eines Parameter-Knotens zu einer unveränderlichen Struktur zum erforschen, wie Unveränderlichkeit Syntaxbäume gilt, Sie führen eine einfache Transformation manuell im Code-Editor und sehen, wie es der Syntaxbaum auswirkt.

Erstellen Sie eine neue C#-Code-Datei, innerhalb Visual Studio 2015 Vorschau (mit der Syntax Visualizer Erweiterung installiert, siehe vorheriger Artikel). Ersetzen Sie alle Inhalte mit dem folgenden Code:

class C
{
  void M()
  }
}

Öffnen Sie die Syntax-Schnellansicht die Optionsfolge Ansicht | Andere Fenster | Roslyn Syntax Visualizer und klicken Sie auf eine beliebige Stelle innerhalb der Codedatei, um den Baum bevölkern. In der Syntax Visual­Izer Fenster mit der rechten Maustaste des Stammknotens der CompilationUnit und wählen Sie Ansicht Directed Syntax-Graph. Visualisierung dieser Syntaxbaum in einem Diagramm wie in Ergebnisse Abbildung 2 (die hier gezeigte Graph lässt die grauen und weißen Trivia-Knoten, die Leerzeichen darstellen). Der blau-Parameter­Liste Syntax Knoten hat zwei grüne Kind-Token, die Klammern und keine blau-Syntax-Knoten darstellt, wie die Liste keine Parameter enthält.

Syntaxbaum vor der Transformation
Abbildung 2 Syntaxbaum vor der Transformation

Die Transformation, die Sie hier simulieren werde ist einer, der einen neuen Parameter vom Typ int hinzuzufügen wäre Geben Sie den Code "Int i" innerhalb der Klammern der Methode M der Parameterliste und beobachten die Änderungen in der Syntax-Visualizer wie Sie geben:

class C
{
  void M(int i)
  {
  }
}

Beachten Sie, dass, noch bevor Sie eingegeben, wenn Ihre unvollständige Code Fehler bei der Kompilierung enthält (in der Syntax-Visualizer als Knoten mit einem roten Hintergrund dargestellt), der Baum noch kohärent ist und der Compiler errät, der neue Code einen gültigen Parameterknoten bilden. Diese Widerstandsfähigkeit der Syntaxbäume zu Compilerfehlern ist, was IDE-Features und Ihre Diagnose gut gegen unvollständigen Code arbeiten können.

Der rechten Maustaste auf den Stammknoten der CompilationUnit wieder und generieren ein neues Diagramm, die aussehen sollen Abbildung 3 (wieder, die hier abgebildete ohne Wissenswertes).

Syntaxbaum nach der Transformation
Abbildung 3 Syntaxbaum nach der Transformation

Beachten Sie, dass die ParameterList nun hat drei Kinder, die zwei Klammern-Token vor sowie einen neuen Parameter Syntax Knoten. Wie Sie eingegeben "Int i" im Editor, Visual Studio ersetzt das Dokument zurück Syntaxbaum mit diesem neuen Syntaxbaum, der neue Quellcode darstellt.

Eignet durchführen einen vollständigen Ersatz sich für kleine Zeichenfolgen, die einzelne Objekte, aber was ist mit Syntaxbäume sind? Eine große Codedatei enthält möglicherweise Tausende oder Zehntausende von Syntax-Knoten, und Sie wollen ja nicht, alle diese Knoten neu erstellt werden, jedes Mal, wenn jemand ein Zeichen innerhalb einer Datei eingibt. Dass würde Unmengen von verwaisten Objekten für den Garbage Collector zu säubern und ernsthaft verletzt Leistung zu generieren.

Glücklicherweise bietet die unveränderliche Natur der Syntax Knoten auch hier die Flucht. Da die meisten der Knoten im Dokument sind nicht betroffen, wenn Sie eine kleine Änderung vornehmen, können dieser Knoten als Kinder in der neuen Struktur sicher wiederverwendet werden. Der Blick hinter die Kulissen interne Knoten, der die Daten für einen bestimmten Syntax Knoten weist nur nach unten an den Knoten Kinder zu speichert. Da die internen Knoten übergeordneten Zeiger nicht, ist es sicher für den gleichen internen Knoten in vielen Wiederholungen eines bestimmten Syntax-Baum, immer und immer wieder auftauchen, so lange, wie dieser Teil des Codes gleich bleibt.

Diese Knoten-Wiederverwendung bedeutet, dass die einzigen Knoten in einer Struktur, die auf jeder Tastenanschlag neu erstellt werden müssen mit mindestens ein Nachkomme, die geändert wurde, nämlich die schmale Kette der übergeordneten Knoten bis zu der Wurzel sind, wie in Abbildung 4. Alle anderen Knoten werden wiederverwendet, da ist.

übergeordneten Knoten während der Transformation ersetzt
Abbildung 4 übergeordneten Knoten während der Transformation ersetzt

In diesem Fall ist die Kern-Änderung zum Erstellen Ihrer neuen Parameterknoten und ersetzen Sie dann die ParameterList durch eine neue ParameterList, die den neuen Parameter als untergeordneter Knoten eingefügt hat. Ersetzen der ParameterList erfordert auch die Kette der übergeordneten Knoten, als jeden Vorfahren Liste der untergeordneten Knoten Änderungen gehören ersetzten Knotens ersetzt. Weiter unten in diesem Artikel tun Sie diese Art von Ersatz für Ihre Regex-Analyzer mit der SyntaxNode.ReplaceNode-Methode, die kümmert sich um alle übergeordneten Knoten für Sie zu ersetzen.

Sie haben nun gesehen, das allgemeine Muster für die Planung einer Codeupdate: Sie beginnen mit Code in der vor dem Staat, der die Diagnose auslöst. Sie nehmen dann manuell die Änderungen, die das Update Beobachtung der Wirkung auf die Syntaxbaum machen sollte. Schließlich erarbeiten Sie den erforderlichen Code zum Erstellen der Ersatz-Knoten und zurück eine neue Syntaxstruktur, die sie enthält.

Werden Sie sicher hast du das Projekt geöffnet ist, mit der Diagnose, die Sie letztes Mal erstellt. Um Ihren Codefix zu implementieren, werden Sie in CodeFixProvider.cs zu graben.

GetFixableDiagnosticIds-Methode

Korrekturen und die Diagnose, die sie zu beheben sind durch diagnostische IDs lose gekoppelt. Jeder Codeupdate richtet sich an eine oder mehrere diagnostische IDs. Wenn Visual Studio eine Diagnose mit einer passenden ID sieht, fragt es sich, dass Netzbetreiber Fix Code, wenn Sie Code behebt zu bieten. Loser Kopplung, basierend auf der ID-Zeichenfolge ermöglicht eine Analyzer eine Diagnose durch ein fremdes Analyzer oder sogar eine Lösung für integrierte Compiler-Fehler und Warnungen produziert einen Fix vorzusehen.

In diesem Fall produziert Ihre Analyzer die Diagnose und das Codeupdate. Sie können sehen, dass die GetFixableDiagnosticIds-Methode bereits die Diagnose ID zurückgekehrt ist, Sie in Ihre Diagnose-Typ definiert, so gibt es nichts, hier zu ändern.

ComputeFixesAsync-Methode

Die ComputeFixesAsync-Methode ist die wichtigste Triebkraft für das Codeupdate. Diese Methode wird aufgerufen, wenn ein oder mehrere passende Diagnose für einen bestimmten Zeitraum von Code gefunden werden.

Sie können sehen, dass die Vorlage Standardimplementierung der ComputeFixesAsync-Methode die erste Diagnose aus dem Kontext zieht (in den meisten Fällen, nur soll man), und bekommt die Diagnose Span. Die nächste Zeile durchsucht dann Syntaxbaum aus dieser Zeitspanne den nächsten Typ-Deklaration-Knoten zu finden. Bei der Standard-Vorlage in der Regel ist den entsprechenden Knoten, deren Inhalt Befestigung benötigt.

In deinem Fall suchte der diagnostische Analyzer schrieb Sie für Aufrufe zu sehen, ob sie Anrufe zu Regex.Match waren. Um Logik zwischen Ihren Diagnose- und Ihren Codefix teilen, ändern Sie die Art erwähnt in der Baum-Suche OfType Filter um den gleichen InvocationExpressionSyntax-Knoten zu finden. Benennen Sie die lokale Variable auf "InvocationExpr," sowie:

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

Sie haben nun einen Verweis auf den gleichen Aufruf-Knoten mit dem diagnostische Analyzer gestartet. In der next-Anweisung übergeben Sie diesen Knoten an die Methode für die Berechnung wird die Codeänderungen werden Sie Sug­Gesting für dieses Update. Umbenennen Sie diese Methode von MakeUppercaseAsync in FixRegexAsync und ändern Sie die Update-Beschreibung Fix Regex:

context.RegisterFix(
  CodeAction.Create("Fix regex", c => FixRegexAsync(
  context.Document, invocationExpr, c)), diagnostic);

Bei jedem Aufruf der Kontext RegisterFix Methode ordnet eine neue Code-Aktion der betreffenden Diagnose Wellenlinie und erzeugt ein Menüelement in der Glühbirne. Beachten Sie, dass Sie nicht wirklich die FixRegexAsync-Methode aufrufen, die noch die Code-Transformation ausführt. Stattdessen wird der Methodenaufruf in einem Lambda-Ausdruck eingeschlossen, die Visual Studio später aufrufen können. Dies liegt daran das Ergebnis der Transformation nur erforderlich ist, wenn der Benutzer tatsächlich Ihr Fix-Regex-Element auswählt. Wenn das Update-Element hervorgehoben oder ausgewählt ist, ruft Visual Studio Ihre Aktion, um eine Vorschau zu generieren oder wenden Sie das Update. Bis dahin vermeidet Visual Studio läuft die Update-Methode, nur für den Fall, dass Sie teure Operationen, z. B. projektmappenweiten Umbenennungen durchführen.

Beachten Sie, dass ein Codeanbieter Fix ist nicht verpflichtet, ein Codeupdate für jede Instanz einer bestimmten Diagnose zu produzieren. Es ist häufig der Fall, den Sie haben ein Update nur für einige Fälle vorschlagen, die Ihre Analyzer gewundener kann. Wenn Sie nur haben behebt einige Zeit, sollten Sie zuerst testen in ComputeFixesAsync alle Bedingungen, die Sie benötigen, um festzustellen, ob Sie die spezifische Situation beheben können. Wenn diese Bedingungen erfüllt, nicht sind, sollten Sie vom ComputeFixesAsync zurückgeben, ohne RegisterFix.

In diesem Beispiel bieten Ihnen eine Lösung für alle Instanzen der Diagnose-, so gibt es keine weitere Bedingungen zu überprüfen.

FixRegexAsync-Methode

Jetzt wirst du das Herz des Code-Fixes. Die FixRegexAsync-Methode, wie Sie derzeit geschrieben nimmt ein Dokument und produziert eine aktualisierte Lösung. Während bestimmte Knoten und Symbole diagnostische Analysatoren ansehe, können Code Korrekturen in der gesamten Projektmappe Code ändern. Sie können sehen, dass der Template-Code hier Renamer.RenameSymbol nennt­Async, die nicht nur das Symbol Typdeklaration, aber auch alle Verweise auf das Symbol in der Lösung ändert.

In diesem Fall wollen Sie nur der Musterzeichenfolge im aktuellen Dokument, lokale Änderungen vornehmen, so können Sie den Rückgabetyp der Methode von Aufgabe ändern<Lösung> Aufgabe<Dokument>. Diese Signatur ist immer noch kompatibel mit der Lambda-Ausdruck in ComputeFixes­Async, als CodeAction.Create hat eine andere Überladung, die ein Dokument, anstatt eine Lösung akzeptiert. Sie müssen auch aktualisieren Sie den TypeDecl-Parameter entsprechend den InvocationExpressionSyntax-Knoten, die, den Sie aus der ComputeFixesAsync-Methode übergeben werden:

private async Task<Document> FixRegexAsync(Document document,
  InvocationExpressionSyntax invocationExpr, CancellationToken cancellationToken)

Weil Sie eines der "Make Großbuchstaben" Logik nicht benötigen, löschen Sie den Hauptteil der Methode sowie.

Suchen den Knoten zu ersetzen im erste Halbjahr Ihr Fixer sieht ähnlich wie die erste Hälfte von Ihrer Diagnose Analyzer — Graben in die InvocationExpression, die relevanten Teile des Methodenaufrufs zu finden, das Ihren Fix informiert werden möchten. In der Tat können Sie nur in der ersten Hälfte der AnalyzeNode-Methode auf die Try-Catch-Block kopieren. Überspringen der ersten Zeile, obwohl, wie Sie bereits haben InvocationExpr als Parameter. Weil Sie, dass es Code handelt wissen, bei denen Sie erfolgreich eine Diagnose gefunden haben, können Sie alle die "If"-Prüfungen entfernen. Die einzige Änderung zu machen soll das semantische Modell aus dem Dokument-Argument zu holen, Sie nicht mehr einen Kontext besitzen, der das semantische Modell direkt bietet.

Wenn Sie die Änderungen abgeschlossen haben, sollte die Stelle Ihrer FixRegexAsync-Methode wie folgt aussehen:

var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var memberAccessExpr =
  invocationExpr.Expression as MemberAccessExpressionSyntax;
var memberSymbol =
  semanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
var argumentList = invocationExpr.ArgumentList as ArgumentListSyntax;
var regexLiteral =
  argumentList.Arguments[1].Expression as LiteralExpressionSyntax;
var regexOpt = semanticModel.GetConstantValue(regexLiteral);
var regex = regexOpt.Value as string;

Generieren den Ersatzknoten jetzt, dass Sie wieder RegexLiteral, die Ihre alten Zeichenfolgenliteral darstellt, müssen Sie das neue zu generieren. Berechnung genau welche Zeichenfolge, die Sie benötigen, um eine willkürliche Regex-Muster zu beheben ist eine große Aufgabe, die weit über den Rahmen dieses Artikels ist. Als Ersatz für jetzt verwenden Sie einfach die Zeichenfolge gültig Regex, d. h. tatsächlich eine gültige Regex Musterzeichenfolge. Wenn Sie sich entscheiden, Ihre eigenen weiter auf, Sie klein anfangen und Ziel Ihrer ansetzen auf ganz besondere Regex-Probleme.

Die Low-Level lässt sich produzieren neue Syntax-Knoten in Ihren Baum zu ersetzen durch die Mitglieder auf SyntaxFactory. Diese Methoden können Sie jede Art von Syntax-Knoten in genau der Form zu erstellen, die Sie wählen. Jedoch beweist es oft einfacher nur den Ausdruck auszuwerten, gewünschte Text, den Compiler die Schwerarbeit um die Knoten zu erstellen machen zu lassen. Um einen Code-Snippet zu analysieren, einfach rufen Sie SyntaxFactory.ParseExpression an und geben Sie den Code für ein Zeichenfolgenliteral:

var newLiteral = SyntaxFactory.ParseExpression("\"valid regex\"");

Dieses neue Literal als Ersatz in den meisten Fällen gut funktionieren würde, aber es fehlt etwas. Wenn Sie sich erinnern, können Syntax-Token Wissenswertes beigefügt werden, die Leerzeichen oder Kommentare darstellt. Sie müssen über alle Wissenswertes aus dem alten literalen Ausdruck um sicherzustellen, dass Sie nicht löschen, Abstände oder Kommentare aus den alten Code Kopieren. Es ist auch empfehlenswert, neue Knoten zu kennzeichnen, die Sie mit der Anmerkung "Formatter" erstellen, die die Code-Update-Engine informiert, die Sie Ihre neuen Knoten entsprechend des Endbenutzers Code Style-Einstellungen formatiert möchten. Sie müssen Hinzufügen einer using Direktive für den Namespace Microsoft.CodeAnalysis.Formatting. Mit diesen Addi­gen, Ihre ParseExpression-Aufruf sieht wie folgt aus:

var newLiteral = SyntaxFactory.ParseExpression("\"valid regex\"")
  .WithLeadingTrivia(regexLiteral.GetLeadingTrivia())
  .WithTrailingTrivia(regexLiteral.GetTrailingTrivia())
  .WithAdditionalAnnotations(Formatter.Annotation);

Durch einschieben der neue Knoten in der Struktur der Syntax da Sie nun einen neuen Syntax-Knoten für das Zeichenfolgenliteral verfügen, können Sie ersetzen den alten Knoten innerhalb der Struktur der Syntax produziert eine neue Struktur mit einer festen Regex-Muster-Zeichenfolge.

Zunächst erhalten Sie den Stammknoten aus dem aktuellen Dokument Syntaxbaum:

var root = await document.GetSyntaxRootAsync();

Jetzt können Sie auf diese Syntax-Stamm Swap den alten Syntax-Knoten und Swap in der neuen die ReplaceNode-Methode aufrufen:

var newRoot = root.ReplaceNode(regexLiteral, newLiteral);

Denken Sie daran, dass Sie hier einen neuen Root-Knoten generieren. Alle Syntax-Knoten ersetzen erfordert auch seine Eltern den Stamm hinauf zu ersetzen. Wie Sie gesehen haben, bevor alle Syntax-Knoten in der .NET-Compiler-Plattform unveränderlich sind. Diese Ersatz-Operation gibt eigentlich nur einen neue Syntax Stammknoten mit dem Zielknoten und dessen Vorgänger ersetzt, wie verwiesen.

Da Sie jetzt eine neue Syntax-Stamm mit einer festen Schnur lit­allgemei, Sie können zu Fuß eine weitere Ebene der Struktur in ein neues Dokumentobjekt zu erzeugen, die Ihre aktualisierte "Root" enthält. Um den Stamm zu ersetzen, verwenden Sie die WithSyntaxRoot-Methode auf das Dokument:

var newDocument = document.WithSyntaxRoot(newRoot);

Dies ist das gleiche mit API-Muster, die, das Sie gerade gesehen, beim Aufrufen von WithLeadingTrivia und andere Methoden für den Ausdruck, den Sie analysiert. Sie sehen dies mit Muster, häufig bei der Umwandlung von vorhandenen Objekten im unveränderlichen Roslyn-Objektmodell. Die Idee ist ähnlich der .NET String.Replace-Methode, die ein neues String-Objekt zurückgibt.

Das umgewandelte Dokument in der Hand können Sie es jetzt von FixRegexAsync zurückgeben:

return newDocument;

Der Code in CodeFixProvider.cs sollte jetzt aussehen wie Abbildung 5.

Abbildung 5 der vollständige Code für CodeFixProvider.cs

using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
namespace RegexAnalyzer
{
  [ExportCodeFixProvider("RegexAnalyzerCodeFixProvider",
    LanguageNames.CSharp), Shared]
  public class RegexAnalyzerCodeFixProvider : CodeFixProvider
  {
    public sealed override ImmutableArray<string> GetFixableDiagnosticIds()
    {
      return ImmutableArray.Create(RegexAnalyzer.DiagnosticId);
    }
    public sealed override FixAllProvider GetFixAllProvider()
    {
      return WellKnownFixAllProviders.BatchFixer;
    }
    public sealed override async Task ComputeFixesAsync(CodeFixContext context)
    {
      var root =
        await context.Document.GetSyntaxRootAsync(context.CancellationToken)
        .ConfigureAwait(false);
      var diagnostic = context.Diagnostics.First();
      var diagnosticSpan = diagnostic.Location.SourceSpan;
      // Find the invocation expression identified by the diagnostic.
      var invocationExpr =   
        root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf()
        .OfType<InvocationExpressionSyntax>().First();
      // Register a code action that will invoke the fix.
      context.RegisterFix(
        CodeAction.Create("Fix regex", c =>
        FixRegexAsync(context.Document, invocationExpr, c)), diagnostic);
    }
    private async Task<Document> FixRegexAsync(Document document,
      InvocationExpressionSyntax invocationExpr,
      CancellationToken cancellationToken)
    {
      var semanticModel =
        await document.GetSemanticModelAsync(cancellationToken);
      var memberAccessExpr =
        invocationExpr.Expression as MemberAccessExpressionSyntax;
      var memberSymbol =
        semanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
      var argumentList = invocationExpr.ArgumentList as ArgumentListSyntax;
      var regexLiteral =
        argumentList.Arguments[1].Expression as LiteralExpressionSyntax;
      var regexOpt = semanticModel.GetConstantValue(regexLiteral);
      var regex = regexOpt.Value as string;
      var newLiteral = SyntaxFactory.ParseExpression("\"valid regex\"")
        .WithLeadingTrivia(regexLiteral.GetLeadingTrivia())
        .WithTrailingTrivia(regexLiteral.GetTrailingTrivia())
        .WithAdditionalAnnotations(Formatter.Annotation);
      var root = await document.GetSyntaxRootAsync();
      var newRoot = root.ReplaceNode(regexLiteral, newLiteral);
      var newDocument = document.WithSyntaxRoot(newRoot);
      return newDocument;
    }
  }
}

Ausprobieren es das war's! Sie haben jetzt einen Codefix definiert, deren Transformation ausgeführt wird, wenn Benutzer Ihre Diagnose stoßen und wählen Sie das Update aus dem Menü "Glühbirne". Um das Codeupdate auszuprobieren, drücken Sie F5, wieder in die Hauptinstanz des Visual Studio und eröffnen Sie damit die Konsolenanwendung. Dieses Mal, wenn Sie den Cursor auf den Notenschlüssel platzieren, sehen Sie eine Glühbirne, die auf der linken Seite angezeigt. Klicken Sie auf die Glühbirne oben ein Menü, das enthält die Fix Regex Code Aktion Sie definiert, wie in dargestellt bringen Abbildung 6. Dieses Menü zeigt einer Vorschau mit einem Inline-Unterschiede zwischen dem alten Dokument und das neue Dokument, dass Sie erstellt, die den Zustand des Codes darstellt, wenn Sie das Update anwenden.

ausprobieren Ihre Codefix
Abbildung 6 ausprobieren Ihre Codefix

Wenn Sie das Menüelement auswählen, Visual Studio nimmt das neue Dokument und nimmt es als den aktuellen Zustand des Puffers Editor für die Quellcode-Datei. Ihre Verlegenheit ist jetzt angewandt worden!

Herzlichen Glückwunsch

Du hast es geschafft! In etwa 70 Gesamt Codezeilen neue identifiziert Sie ein Problem im Benutzercode, live, er ist die Eingabe, verschnörkelte es rot als Fehler und tauchte eine Code-Korrektur, die es bereinigen kann. Sie verwandelt Syntaxbäume und generiert neue Syntax-Knoten auf dem Weg, alles während des Betriebs innerhalb Ihres vertrauten Zieldomäne von regulären Ausdrücken.

Während Sie kontinuierlich verfeinern können die Diagnose und die Code-Fixes, die Sie schreiben, ich habe festgestellt, dass Analysatoren mit der .NET-Compiler-Plattform gebaut Lass dich eine ganze Menge getan innerhalb kurzer Zeit. Sobald Sie komfortable Gebäude Analysatoren erhalten, dann beginnen Sie vor Ort alle möglichen gemeinsamen Probleme in Ihrem täglichen Leben Codierung und erkennen sich wiederholende Korrekturen können Sie automatisieren.

Was werden Sie analysieren?


Alex Turner ist senior Programmmanager für die verwalteten Sprachen-Team bei Microsoft, wo er hat wurde Brauen c# und Visual Basic Güte an dem Projekt .NET-Compiler-Plattform ("Roslyn"). Er hat beendete sein Studium mit einem Master Of Science in Informatik an der Stony Brook University und auf der Build, PDC, TechEd, TechDays und MIX.

Dank den folgenden technischen Experten von Microsoft für die Überprüfung dieses Artikels: Bill Chiles und Lucian Wischik
Lucian Wischik ist auf der VB/C# Sprache Design-Team bei Microsoft mit besonderer Verantwortung für VB. Vor seiner Tätigkeit bei Microsoft arbeitete er in der Wissenschaft auf Concurrency-Theorie und Async. Er ist ein begeisterter Segler und Langstrecken-Schwimmer.

Bill Chiles an Sprachen (CMU Common Lisp, Dylan, IronPython und c#) gearbeitet und Entwickler-tools die meiste Zeit seiner Karriere.  Er verbrachte die letzten 17 Jahre in Microsofts Developer Division Arbeiten über alles, von Visual Studio Kernfeatures, um die Dynamic Language Runtime, c#).