Cet article a fait l'objet d'une traduction automatique.

C#

Ajout d'un réparateur de code à votre analyseur Roslyn

Alex Turner

Si vous avez suivi les étapes dans mon précédent article, « Utilisation Roslyn à écrire un Code Analyzer pour votre API Live » (msdn.microsoft.com/magazine/dn879356), vous avez écrit un analyseur qui affiche les erreurs direct pour les chaînes de modèle non valide expression régulière (regex). Chaque modèle non valide obtient un tilde rouge dans l'éditeur, tout comme vous verriez des erreurs du compilateur et les tildes d'apparaissent en direct que vous tapez votre code. Ceci est rendu possible par la nouvelle plateforme de compilateur .NET ("Roslyn") API, qui puissance c# et Visual Basic édition des expériences en Visual Studio 2015 prévisualiser.

Vous pouvez faire plus ? Si vous avez la connaissance du domaine de voir non seulement ce qui est faux mais aussi comment résoudre ce problème, vous pouvez suggérer le correctif de code pertinent par le biais de la nouvelle ampoule de Visual Studio . Le correctif de ce code permettra un développeur à l'aide de votre analyseur de trouver non seulement une erreur dans son code, il peut aussi le nettoyer instantanément.

Dans cet article, je vais vous montrer comment ajouter un fournisseur de code de correction à votre analyseur de diagnostique de regex qui propose des correctifs à chaque tilde regex. Le correctif sera ajouté comme un élément dans le menu de l'ampoule, laissant l'utilisateur obtenir un aperçu de la difficulté et l'appliquer à son code automatiquement.

Picking Up où vous l'avez laissé

Pour commencer, assurez-vous que vous avez suivi les étapes décrites dans l'article précédent. Dans cet article, je vous ai montré comment écrire la première moitié de votre analyseur, qui génère les tildes Diagnostics sous chaque chaîne de modèle regex invalide. Cet article vous avez traversé :

  • Installation Visual Studio 2015 aperçu, son SDK et les paquets de Roslyn VSIX pertinentes.
  • Création d'un nouveau Diagnostic avec difficulté de Code projet.
  • Ajout de code pour DiagnosticAnalyzer.cs de mettre en œuvre la détection du modèle regex invalide.

Si vous cherchez à rattraper rapidement, consultez Figure 1, ­qui répertorie le code final pour DiagnosticAnalyzer.cs.

Figure 1 le Code complet pour 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);
      }
    }
  }
}

Transformation des arbres de syntaxe immuable

La dernière fois, quand vous avez écrit l'analyseur diagnostic pour détecter les modes regex invalide, la première étape consistait à utiliser le visualiseur de syntaxe pour définir les modèles dans les arbres de syntaxe qui indiquent le code de problème. Ensuite, vous avez écrit une méthode d'analyse qui a eu lieu à chaque fois que le type de nœud pertinent a été trouvée. La méthode vérifiée pour la configuration des nœuds de syntaxe qui justifiaient un tilde erreur.

Une difficulté d'écriture est un processus similaire. Vous traitez dans les arbres de syntaxe, mettant l'accent sur l'état souhaité de nouveau les fichiers de code après que l'utilisateur applique votre correctif. La plupart des corrections de code impliquent d'ajouter, de supprimer ou de remplacer les nœuds de syntaxe des arbres actuelles pour produire de nouveaux arbres de syntaxe. Vous pouvez commander directement sur des nœuds de la syntaxe ou utilisation API qui vous permettre d'apporter des modifications de l'échelle du projet, tels que renomme.

Une propriété très importante à comprendre sur les nœuds de la syntaxe, les arbres et les symboles dans la plate-forme du compilateur .NET, c'est qu'ils sont immuables. Une fois qu'un nœud de syntaxe ou un arbre est créé, il n'est pas modifiable, un arbre ou un nœud objet représentera toujours le même code c# ou Visual Basic .

Immuabilité dans une API pour transformer le code source peut sembler paradoxale. Comment pouvez-vous ajouter, supprimer et remplacer les nœuds enfants dans un arbre de syntaxe si ni les arbres ni ses nœuds peuvent être changés ? Ici, il est utile d'examiner le type de chaîne .NET, un autre type immuable que vous utilisez très probablement tous les jours. Vous effectuez des opérations pour transformer les chaînes assez souvent, concaténer ensemble et même remplacement de sous-chaînes utilisant String.Replace. Toutefois, aucune de ces opérations réellement changer l'objet initial de la chaîne. Au lieu de cela, chaque appel retourne un nouvel objet chaîne qui représente le nouvel état de la chaîne. Vous pouvez assigner cet objet nouveau retour à votre variable d'origine, mais n'importe quelle méthode que vous avez passé la corde usagée à aura toujours la valeur d'origine.

Ajout d'un nœud de paramètre d'une arborescence de l'immuable pour explorer comment l'immuabilité s'applique aux arbres de syntaxe, vous effectuez une simple transformation manuellement dans l'éditeur de code et voir comment il affecte l'arbre syntaxique.

À l'intérieur de l'aperçu de Visual Studio 2015 (avec l'extension de syntaxe Visualizer installée, voir article précédent), créez un nouveau fichier de code c#. Remplacez son contenu par le code suivant :

class C
{
  void M()
  }
}

Ouvrez le visualiseur de syntaxe en choisissant affichage | Autres fenêtres | Roslyn syntaxe Visualizer et cliquez n'importe où dans le fichier de code pour remplir l'arborescence. Dans la syntaxe Visual­izer fenêtre, cliquez sur le nœud de CompilationUnit racine et choisissez graphique de syntaxe réalisé la vue. Visualiser cet arbre de syntaxe se traduit par un graphique comme celui de Figure 2 (le graphique ci-contre omet les nœuds trivia gris et blanc qui représentent des espaces blancs). Le paramètre bleu­liste syntaxe noeud possède deux jetons vert enfant représentant ses parenthèses et aucun nœud de syntaxe enfant bleu, étant donné que la liste ne contient aucun paramètre.

arborescence de syntaxe avant la transformation
Figure 2 arborescence de syntaxe avant la transformation

La transformation que vous pourrez simuler ici est celle qui ajouterait un nouveau paramètre de type int. Tapez le code « int je » dans les parenthèses de la méthode de M liste des paramètres et montre les changements dans le visualiseur de syntaxe que vous tapez :

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

Notez qu'avant même que vous avez fini de taper, lorsque votre code incomplet contient des erreurs de compilation (montrés dans le visualiseur de syntaxe en tant que nœuds avec un fond rouge), l'arbre est toujours cohérent et le compilateur devine que votre nouveau code forment un nœud de paramètre valide. Cette résilience des arbres de syntaxe pour les erreurs du compilateur est ce qui permet des fonctionnalités IDE et vos diagnostics à fonctionnent bien contre code incomplète.

Faites un clic droit sur le nœud de racine CompilationUnit à nouveau et de générer un nouveau graphe, qui devrait ressembler à Figure 3 (encore une fois, représenté ici sans anecdotes).

Arbre syntaxique après la transformation
Arbre syntaxique figure 3 après la transformation

Notez que le ParameterList a maintenant trois enfants, les jetons de deux parenthèses, qu'il disposait, ainsi qu'un nouveau nœud de syntaxe de paramètre. Comme vous l'avez tapé "int je" dans l'éditeur, Visual Studio a remplacé arborescence de syntaxe du document précédent avec cette nouvelle arborescence de syntaxe qui représente votre nouveau code source.

Effectuer un remplacement intégral fonctionne assez bien pour les petites chaînes, qui sont des objets simples, mais qu'en est-il des arbres de syntaxe ? Un fichier de code grand peut contenir des milliers ou des dizaines de milliers de nœuds de la syntaxe, et vous ne voulez certainement pas tous ces nœuds pour être recréée à chaque fois qu'une personne tape un caractère dans un fichier. Qui générerait des tonnes d'objets orphelins le garbage collector nettoyer et sérieusement altérer les performances.

Heureusement, la nature immuable des nœuds syntaxe fournit également l'évasion ici. Parce que la plupart des nœuds dans le document ne sont pas affectée lorsque vous faites un petit changement, ces nœuds peuvent être réutilisés en toute sécurité que les enfants dans la nouvelle arborescence. Le nœud interne dans les coulisses qui stocke les données d'un nœud donné syntaxe pointe uniquement vers le bas aux enfants du nœud. Parce que les nœuds internes n'ont pointeurs de parent, il est sûr pour le même nœud interne s'affiche maintes et maintes fois dans le nombre d'itérations d'un arbre de syntaxe donnée, aussi longtemps que cette partie du code reste le même.

Cette réutilisation de nœud signifie que les seuls nœuds dans une arborescence qui doivent être recréés après chaque frappe au clavier sont ceux qui ont au moins un descendant qui a changé, à savoir la chaîne étroite de nœuds ancêtres jusqu'à la racine, comme le montre Figure 4. Tous les autres nœuds sont réutilisés comme c'est.

les nœuds ancêtres remplacés au cours de la transformation
Figure 4 les nœuds ancêtres remplacés au cours de la transformation

Dans ce cas, le changement de base est de créer votre nouveau nœud de paramètre et ensuite remplacer le ParameterList avec un nouveau ParameterList qui possède le paramètre nouveau inséré comme un nœud enfant. Remplaçant le ParameterList exige également le remplacement de la chaîne des nœuds ancêtres, comme la liste de chaque ancêtre de changements nœuds enfant d'inclure le nœud remplacé. Plus loin dans cet article, vous allez faire ce genre de remplacement pour votre analyseur d'expression régulière avec la méthode SyntaxNode.ReplaceNode, qui s'occupe du remplacement de tous les nœuds ancêtres pour vous.

Vous avez maintenant vu le modèle général pour la planification d'un correctif de code : Vous commencez par le code de l'avant l'État qui déclenche le diagnostic. Puis vous modifiez manuellement que le correctif devrait faire, en observant l'effet sur l'arborescence de syntaxe. Enfin, vous vous entraînez le code nécessaire pour créer les nœuds de remplacement et de retourner une nouvelle arborescence de syntaxe qui les contient.

Veillez à ce que vous avez votre projet ouvert, contenant le diagnostic que vous avez créé la dernière fois. Pour implémenter le correctif de votre code, vous allez creuser dans CodeFixProvider.cs.

Méthode GetFixableDiagnosticIds

Correctifs et les diagnostics qu'ils résolvent sont faiblement couplés par IDs diagnostiques. Chaque correctif de code cible un ou plusieurs ID de diagnostic. Quand Visual Studio voit un diagnostic avec l'ID correspondant, il va demander à votre fournisseur de code de correction si vous avez le code fixe à offrir. Couplage faible, selon la chaîne d'ID permet un analyseur pour fournir un correctif pour un diagnostic produit par l'analyseur de quelqu'un d'autre, ou même une difficulté pour les avertissements et les erreurs du compilateur intégré.

Dans ce cas, votre analyseur produit le diagnostic et le correctif de code. Vous pouvez voir que la méthode GetFixableDiagnosticIds retourne déjà l'ID diagnostic vous avez défini dans votre type de Diagnostic, donc il n'y a rien à changer ici.

Méthode ComputeFixesAsync

La méthode ComputeFixesAsync est le principal moteur pour le correctif de code. Cette méthode est appelée chaque fois qu'un ou plusieurs Diagnostics correspondants sont trouvent pour une durée donnée du code.

Vous pouvez voir qu'implémentation par défaut du modèle de la méthode de ComputeFixesAsync sort le premier diagnostic du contexte (dans la plupart des cas, vous n'attendez un) et obtient la durée du diagnostic. La ligne suivante puis recherche dans l'arbre syntaxique de cet intervalle pour rechercher le nœud de déclaration de type le plus proche. Dans le cas de la règle de modèle par défaut, qui est le nœud pertinent dont le contenu nécessaire de fixation.

Dans votre cas, invocations voir s'il s'agissait d'appels à Regex.Match cherchait l'analyseur diagnostic que vous a écrit. Afin de partager la logique entre votre diagnostic et votre correctif de code, remplacez le type mentionné dans le filtre de recherche de l'arbre à OfType pour trouver ce même nœud de InvocationExpressionSyntax. Renommer la variable locale à « invocationExpr, » ainsi :

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

Vous avez maintenant une référence au même nœud invocation avec lequel l'analyseur diagnostic a commencé. Dans le prochain relevé, vous passez ce nœud à la méthode qui permettra de calculer les changements de code vous serez sug­gesting pour ce correctif. Renommez cette méthode à partir de MakeUppercaseAsync à FixRegexAsync et modifiez la description de fix en Fix regex :

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

Chaque appel à la méthode du contexte RegisterFix associe une nouvelle action de code le tilde diagnostique en question et va produire un élément de menu à l'intérieur de l'ampoule. Notez que vous n'appelez pas en fait la méthode FixRegexAsync qui effectue la transformation de code encore. Au lieu de cela, l'appel de méthode est enveloppé dans une expression lambda qui Visual Studio peut appeler plus tard. C'est parce que le résultat de votre transformation n'est nécessaire que lorsque l'utilisateur sélectionne en fait votre article de regex de Fix. Lorsque le point fixe est mis en surbrillance ou choisi, Visual Studio appelle votre action pour générer un aperçu ou d'appliquer le correctif. Jusque là, Visual Studio évite exécutant votre méthode de correction, juste au cas où que vous effectuez des opérations coûteuses, comme les changements de noms à l'échelle de la solution.

Notez qu'un fournisseur de code de correction n'est pas obligé de produire un correctif de code pour chaque instance d'un diagnostic donné. C'est souvent le cas que vous avez une difficulté à ne penser que pour certains des cas que votre analyseur peut en tire-bouchon. Si vous aurez seulement corrige certains du temps, vous devez d'abord tester dans ComputeFixesAsync n'importe quelles conditions, vous devez déterminer si vous pouvez corriger la situation spécifique. Si ces conditions ne sont pas remplies, vous devez retourner de ComputeFixesAsync sans appeler RegisterFix.

Pour cet exemple, vous offrirons un correctif pour toutes les instances du diagnostic, il n'y a pas plus de conditions à vérifier.

Méthode FixRegexAsync

Maintenant, vous aller au cœur de la correction de code. La méthode de FixRegexAsync tel qu'il est écrite prend un Document et produit une Solution de mise à jour. Alors que les analyseurs de diagnostics regarder des symboles et des nœuds spécifiques, des corrections de code peuvent modifier code dans l'ensemble de la solution. Vous pouvez voir que le code du modèle ici appelle Renamer.RenameSymbol­Async, qui modifie non seulement les déclaration de type de symbole, mais aussi toutes les références à ce symbole dans l'ensemble de la solution.

Dans ce cas, vous ne souhaitez apporter des modifications locales à la chaîne de modèle dans le document actuel, donc vous pouvez changer le type de retour de la méthode de la tâche<Solution> à la tâche<Document>. Cette signature est toujours compatible avec l'expression lambda dans ComputeFixes­Async, comme CodeAction.Create a une autre surcharge qui accepte un Document plutôt qu'une Solution. Vous aurez également besoin de mettre à jour le paramètre typeDecl pour faire correspondre le nœud InvocationExpressionSyntax que tu es de passage à partir de la méthode ComputeFixesAsync :

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

Parce que vous n'avez pas besoin de la logique de « make majuscules », supprimez le corps de la méthode, aussi bien.

Trouver le nœud à remplacer la première moitié de votre fixateur se ressemblent beaucoup à la première moitié de votre analyseur de diagnostic — vous devez creuser la InvocationExpression pour trouver les parties pertinentes de l'appel de méthode qui éclairera votre correctif. En fait, vous pouvez simplement copier dans la première moitié de la méthode de AnalyzeNode vers le bas pour le bloc try-catch. Sauter la première ligne, cependant, comme vous l'avez déjà prendre invocationExpr en tant que paramètre. Parce que vous savez, qu'il s'agit de code pour lequel vous avez trouvé avec succès de diagnostic, vous pouvez supprimer tous les contrôles « if ». Le seul autre changement faire est chercher le modèle sémantique de l'argument du Document, que vous n'avez plus un contexte qui fournit le modèle sémantique directement.

Lorsque vous avez terminé les modifications, le corps de votre méthode de FixRegexAsync devrait ressembler à ceci :

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;

Générer le nœud de remplacement maintenant que vous avez à nouveau regexLiteral, qui représente votre vieille chaîne littérale, vous devez générer une nouvelle. Calculer exactement quelle chaîne vous devrez fixer un motif de regex arbitraire est une tâche importante qui est bien au-delà de la portée de cet article. Comme un remplaçant pour l'instant, vous utiliserez juste la regex valide de chaîne, qui est en fait une chaîne de modèle regex valide. Si vous décidez d'aller plus loin votre propre, vous devriez commencer petit et cibler votre Difficulté à des problèmes très particuliers de regex.

Pour produire de nouveaux nœuds de syntaxe à substituer dans votre arbre, le bas niveau consiste à travers les membres sur SyntaxFactory. Ces méthodes vous permettent de créer tous les types de nœud de la syntaxe dans exactement la forme que vous choisissez. Toutefois, il s'avère souvent plus facile à analyser juste l'expression souhaitée du texte, laisser le compilateur faire tous le gros du travail pour créer les nœuds. Pour analyser un extrait de code, juste appeler SyntaxFactory.ParseExpression et indiquez le code pour un littéral de chaîne :

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

Ce nouveau littéral fonctionnerait bien pour remplacer dans la plupart des cas, mais il manque quelque chose. Si vous vous souvenez, jetons de syntaxe peuvent avoir attaché trivia qui représente un espace blanc ou des commentaires. Vous aurez besoin de copier sur n'importe quel jeu-questionnaire de la vieille expression littérale pour s'assurer que vous ne supprimez pas n'importe quel espacement ou commentaires de l'ancien code. Il est également conseillé de marquer de nouveaux nœuds, que vous créez avec l'annotation « Formateur », qui informe le moteur de correction de code que vous souhaitez que votre nouveau nœud formatée selon les paramètres de style pour le code de l'utilisateur final. Vous aurez besoin d'ajouter un à l'aide de la directive pour l'espace de noms Microsoft.CodeAnalysis.Formatting. Avec ces addi­tions, votre appel ParseExpression ressemble à ceci :

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

Échangeant le nouveau nœud dans l'arborescence de syntaxe maintenant que vous avez un nouveau nœud de syntaxe pour le littéral de chaîne, vous pouvez remplacer l'ancien nœud dans l'arborescence de syntaxe, produisant un nouvel arbre avec une chaîne de modèle regex fixe.

Tout d'abord, vous obtenez le nœud racine de l'arborescence de syntaxe du document actuel :

var root = await document.GetSyntaxRootAsync();

Maintenant, vous pouvez appeler la méthode ReplaceNode sur cette racine de syntaxe pour le swap sur l'ancien nœud de syntaxe et de swap dans le nouveau :

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

N'oubliez pas que vous générez un nouveau nœud racine ici. Remplacer n'importe quel nœud de syntaxe aussi vous oblige à remplacer ses parents tout le chemin jusqu'à la racine. Comme vous avez vu avant, tous les nœuds de la syntaxe dans la plate-forme du compilateur .NET sont immuables. Cette opération de remplacement retourne en fait juste un nouveau nœud de syntaxe de racine avec le noeud cible et ses ancêtres remplacés selon les directives.

Maintenant que vous avez une nouvelle racine de syntaxe avec une chaîne fixe allumée­eral, vous pouvez remonter un plus niveau de l'arbre de produire un nouvel objet Document qui contient la racine de votre mise à jour. Pour remplacer la racine, utilisez la méthode WithSyntaxRoot sur le Document :

var newDocument = document.WithSyntaxRoot(newRoot);

Il s'agit de la même tendance avec API que vous avez vu juste lors de l'appel de WithLeadingTrivia et autres méthodes sur l'expression que vous analysée. Vous verrez cela avec motif souvent lors de la transformation des objets existants dans le modèle objet immuable de Roslyn. L'idée est similaire à la méthode String.Replace .NET qui retourne un nouvel objet chaîne.

Avec le document transformé en main, vous pouvez maintenant le retourner de FixRegexAsync :

return newDocument;

Votre code dans CodeFixProvider.cs devrait maintenant ressembler à Figure 5.

Figure 5 le Code complet pour 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;
    }
  }
}

Essayer il c'est tout ! Vous avez maintenant défini un correctif de code dont la transformation s'exécute lorsque les utilisateurs rencontrent votre diagnostic et choisissent le correctif dans le menu de l'ampoule. Pour essayer le correctif de code, appuyez sur F5 à nouveau dans l'instance principale de Visual Studio et ouvrir l'application console. Cette fois, lorsque vous placez le curseur sur votre ligne ondulée, vous devriez voir une ampoule apparaissent à gauche. En cliquant sur l'ampoule doit faire apparaître un menu qui contient l'action de code regex Fix vous défini, comme le montre Figure 6. Ce menu affiche un aperçu avec un inline de diff entre le Document ancien et le nouveau Document créé, qui représente l'état de votre code si vous choisissez d'appliquer le correctif.

essayer votre Code Fix
Figure 6 essayer votre Code Fix

Si vous sélectionnez cet élément de menu, Visual Studio prend le nouveau Document et l'adopte en l'état actuel de la mémoire tampon de l'éditeur de ce fichier source. Votre Difficulté est maintenant appliquée !

Félicitations

Vous l'avez fait ! Dans environ 70 lignes totales du nouveau code, vous identifié un problème dans votre code utilisateur, vivant, comme il est de taper, squiggled il rouge comme une erreur et refait surface un correctif de code qui peut le nettoyer. Vous transformé des arbres de syntaxe et généré de nouveaux nœuds de syntaxe le long du chemin, tout en opérant au sein de votre domaine cible familière des expressions régulières.

Alors que vous pouvez l'affiner en permanence les diagnostics et les corrections de code vous écrire, j'ai trouvé que les analyseurs construit avec la plate-forme de compilateur .NET vous permettre de faire tout à fait beaucoup de choses dans un court laps de temps. Une fois que vous obtenez des analyseurs de bâtiment confortable, vous commencerez à apercevoir toutes sortes de problèmes communs dans votre quotidien de vie de codage et de détecter les bugs répétitifs, vous pouvez automatiser.

Ce qui va vous analyser ?


Alex Turner est responsable de programme senior pour l'équipe de langages managés chez Microsoft, où il a brassé jusqu'à bonté c# et Visual Basic sur le projet de plate-forme de compilateur .NET ("Roslyn »). Il a obtenu une maîtrise ès sciences en informatique de l'Université de Stony Brook et a parlé à construire, PDC, TechEd, TechDays et MIX.

Remercie les experts techniques Microsoft suivants pour avoir relu cet article : Bill Chiles et Lucian Wischik
Lucian Wischik est sur l'équipe VBde conception de le langue des /C# chez Microsoft, avec une responsabilité particulière pour VB. Avant de rejoindre Microsoft, il a travaillé dans le milieu universitaire sur la théorie de simultanéité et async. Il est un marin vif et un nageur de longue distance.

Bill Chiles a travaillé sur des langues (CMU Common Lisp, Dylan, IronPython et c#) et développeur des outils essentiel de sa carrière.  Il a passé les 17 dernières années dans la Division de Microsoft développeur travaillant sur des sujets allant des principales fonctionnalités de Visual Studio , le Dynamic Language Runtime, pour c#).