<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mikhail Nefedov</title>
    <description>The latest articles on DEV Community by Mikhail Nefedov (@mikhailnefedov).</description>
    <link>https://dev.to/mikhailnefedov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2896924%2Fdd8984aa-521c-47ad-8f62-dbff6fa99a46.png</url>
      <title>DEV Community: Mikhail Nefedov</title>
      <link>https://dev.to/mikhailnefedov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mikhailnefedov"/>
    <language>en</language>
    <item>
      <title>Roslyn CodeFix for updating code</title>
      <dc:creator>Mikhail Nefedov</dc:creator>
      <pubDate>Sun, 13 Jul 2025 15:10:00 +0000</pubDate>
      <link>https://dev.to/mikhailnefedov/roslyn-codefix-for-updating-code-33a4</link>
      <guid>https://dev.to/mikhailnefedov/roslyn-codefix-for-updating-code-33a4</guid>
      <description>&lt;p&gt;Changes are inevitable when you are working with many repositories. As your applications and codebases evolve, methods are marked as obsolete and usage patterns fall out of favor. But how do you tackle a piece of code that’s been used hundreds of times and now needs to be replaced?&lt;/p&gt;

&lt;p&gt;Manual refactorings of the occurrences can take a lot of time. That's where Roslyn code analyzers and CodeFixes may shine.&lt;/p&gt;

&lt;p&gt;In this blog post, I’ll walk through how to build a simple Roslyn CodeFix that upgrades problematic code. For illustration, I will be using a real-world example: the &lt;code&gt;IsNullOrEmpty()&lt;/code&gt; from &lt;code&gt;Microsoft.IdentityModel.Tokens&lt;/code&gt;. This method was mistakenly published as &lt;code&gt;public&lt;/code&gt; and later corrected to &lt;code&gt;internal&lt;/code&gt; in the 8.0.0 release. &lt;/p&gt;

&lt;p&gt;Note: The shown CodeFix may not work on every usage, but is perfect to illustrate some points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scanning for the Problem: Writing the Analyzer
&lt;/h2&gt;

&lt;p&gt;Before we can fix anything, we need to find it. That’s where Roslyn analyzers come in. Roslyn analyzers let us inspect source code during compilation and raise diagnostics when a specific pattern is found. In our case, we’re looking for any usage of the now-internal &lt;code&gt;CollectionUtilities.IsNullOrEmpty()&lt;/code&gt; method from &lt;code&gt;Microsoft.IdentityModel.Tokens&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The analyzer below registers a syntax node action on every method invocation. If it spots a method named &lt;code&gt;IsNullOrEmpty&lt;/code&gt; whose containing type matches &lt;code&gt;Microsoft.IdentityModel.Tokens.CollectionUtilities&lt;/code&gt;, it reports a warning diagnostic.&lt;/p&gt;

&lt;p&gt;Key parts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Diagnostic ID &amp;amp; Descriptor: This defines a unique ID, a readable message, and the severity level.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Initialize Method: Sets up the analyzer to run concurrently and skip generated code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AnalyzeInvocation: The heart of the logic which inspects each method call and checks whether it matches the one we want to flag.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Immutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CSharp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CSharp.Syntax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CodeFixDemo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Analyzer detecting usages of IsNullOrEmpty() from Microsoft.IdentityModel.Tokens&lt;/span&gt;
&lt;span class="c1"&gt;/// package. &lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DiagnosticAnalyzer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LanguageNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CSharp&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IsNullOrEmptyAnalyzer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DiagnosticAnalyzer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Preferred format of DiagnosticId is Your Prefix + Number, e.g. CA1234.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DiagnosticId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DIAG0001"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Use custom IsNullOrEmpty instead of falsely exposed CollectionUtilities extension method"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;MessageFormat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Replace usage of Microsoft.IdentityModel.Tokens.CollectionUtilities.IsNullOrEmpty()"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Category of the diagnostic -&amp;gt; in this case Usage.&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Usage"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;DiagnosticDescriptor&lt;/span&gt; &lt;span class="n"&gt;Rule&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DiagnosticId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DiagnosticSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;ImmutableArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DiagnosticDescriptor&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SupportedDiagnostics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="n"&gt;ImmutableArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnalysisContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// You must call this method to avoid analyzing generated code.&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureGeneratedCodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GeneratedCodeAnalysisFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Allow concurrent execution for better performance.&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableConcurrentExecution&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterSyntaxNodeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnalyzeInvocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SyntaxKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvocationExpression&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AnalyzeInvocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SyntaxNodeAnalysisContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;InvocationExpressionSyntax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SemanticModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSymbolInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;!).&lt;/span&gt;&lt;span class="n"&gt;Symbol&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;IMethodSymbol&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;ContainingType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToDisplayString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Microsoft.IdentityModel.Tokens.CollectionUtilities"&lt;/span&gt;
            &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"IsNullOrEmpty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReportDiagnostic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Diagnostic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;!.&lt;/span&gt;&lt;span class="nf"&gt;GetLocation&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a Roslyn CodeFix
&lt;/h2&gt;

&lt;p&gt;After detecting problematic code with our analyzer, the next step is to help developers fix it. This is where the CodeFixProvider comes into play. It listens for diagnostics we raised and offers users a one-click action to rewrite the code safely and consistently.&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;IsNullOrEmptyCodeFixProvider&lt;/code&gt; listens for our analyzer's &lt;code&gt;DIAG0001&lt;/code&gt; diagnostic. When it detects a flagged usage of the &lt;code&gt;IsNullOrEmpty()&lt;/code&gt; method from &lt;code&gt;CollectionUtilities&lt;/code&gt;, it transforms that code into a more reliable inline check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key Components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FixableDiagnosticIds&lt;/code&gt;: Links this CodeFix to the diagnostic emitted by our analyzer&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GetFixAllProvider()&lt;/code&gt; returns &lt;code&gt;WellKnownFixAllProviders.BatchFixer&lt;/code&gt; to allow batched fixing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RegisterCodeFixesAsync&lt;/code&gt;: Registers the code fix with a title and links to the actual rewrite logic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ReplaceIsNullOrEmptyInvocationAsync&lt;/code&gt;: Constructs a new expression and imports &lt;code&gt;System.Linq&lt;/code&gt; if needed
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Immutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Linq&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CodeActions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CodeFixes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CSharp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CSharp.Syntax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.Editing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CodeFixDemo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;ExportCodeFixProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LanguageNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CSharp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;nameof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmptyCodeFixProvider&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IsNullOrEmptyCodeFixProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CodeFixProvider&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// We use the static BatchFixer.&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;see href="https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md#spectrum-of-fixall-providers"/&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;FixAllProvider&lt;/span&gt; &lt;span class="nf"&gt;GetFixAllProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;WellKnownFixAllProviders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BatchFixer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="n"&gt;ImmutableArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FixableDiagnosticIds&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="n"&gt;ImmutableArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsNullOrEmptyAnalyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiagnosticId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RegisterCodeFixesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CodeFixContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSyntaxRootAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;FindNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;InvocationExpressionSyntax&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterCodeFix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;CodeAction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Replace with is null || .Any() is false"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;createChangedSolution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReplaceIsNullOrEmptyInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;equivalenceKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ReplaceWithIsNullOrAny"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diagnostics&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReplaceIsNullOrEmptyInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;InvocationExpressionSyntax&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;DocumentEditor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Expression&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChildNodes&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;OfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IdentifierNameSyntax&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newExpression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SyntaxFactory&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseExpression&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is null || &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.Any() is false"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithLeadingTrivia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetLeadingTrivia&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTrailingTrivia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTrailingTrivia&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReplaceNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newExpression&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newDocument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChangedDocument&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;newDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSyntaxRootAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;CompilationUnitSyntax&lt;/span&gt; &lt;span class="n"&gt;compilationUnit&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;compilationUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Usings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"System.Linq"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;linqUsing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SyntaxFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsingDirective&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SyntaxFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"System.Linq"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newRoot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;compilationUnit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddUsings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linqUsing&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;newDocument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithSyntaxRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newRoot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Solution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rider users will see the CodeFix option alongside other suggestions in the lightbulb menu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froohw314g62hq1i277nn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Froohw314g62hq1i277nn.png" alt="Roslyn action in Rider" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the CodeFix code
&lt;/h2&gt;

&lt;p&gt;You can find all the code shown in this post in my &lt;a href="https://github.com/mikhailnefedov/CodeFixDemo" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. While building the CodeFix itself was rewarding, what surprised me was how useful and flexible the testing infrastructure turned out to be.&lt;/p&gt;

&lt;p&gt;I started with the Roslyn Analyzer project template, which gives you an excellent out-of-the-box test setup. Especially if you don’t rely on external dependencies. But once you beginworking with external packages like Microsoft.IdentityModel.Tokens, adaptations to the tests need to be made to handle those references properly.&lt;/p&gt;

&lt;p&gt;It is important to note the explicit usage of the CSharpCodeFixTest. This allows us to add additional assembly references (In this case: Microsoft.IdentityModel.Tokens).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Immutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.IO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading.Tasks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.CSharp.Testing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.Testing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.CodeAnalysis.Testing.Verifiers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Xunit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;CodeFixDemo.Tests&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IsNullOrEmptyCodeFixProviderTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;

public class Examples
{
    public void Example1() {
        IEnumerable&amp;lt;int&amp;gt; myEnumerable = new[] { 1, 2, 3, 4};
        if (myEnumerable.IsNullOrEmpty())
        {
            // do something
        }
    }
}
"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;expectedCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.Linq;

public class Examples
{
    public void Example1() {
        IEnumerable&amp;lt;int&amp;gt; myEnumerable = new[] { 1, 2, 3, 4};
        if (myEnumerable is null || myEnumerable.Any() is false)
        {
            // do something
        }
    }
}
"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;codeFixTest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CSharpCodeFixTest&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;
            &lt;span class="n"&gt;IsNullOrEmptyAnalyzer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IsNullOrEmptyCodeFixProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;XUnitVerifier&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;TestCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ReferenceAssemblies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ReferenceAssemblies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"net8.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PackageIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"Microsoft.NETCore.App.Ref"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"8.0.0"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                    &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ref"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"net8.0"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPackages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ImmutableArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PackageIdentity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.IdentityModel.Tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"7.0.0"&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;codeFixTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExpectedDiagnostics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DiagnosticResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"DIAG0001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CodeAnalysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiagnosticSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warning&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;WithLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;codeFixTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FixedCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expectedCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Act &amp;amp; Assert&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;codeFixTest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping it up
&lt;/h2&gt;

&lt;p&gt;One thing I particularly appreciated while building this CodeFix: we didn’t need to import or rely on the external NuGet package in the analyzer code. That means our analyzer and the tests around remain untouched by future breaking changes. Since the example showcased here specifically targets &lt;code&gt;Microsoft.IdentityModel.Tokens&lt;/code&gt; versions before 8.0.0, this isolation is a huge win for long-term maintainability.&lt;/p&gt;

&lt;p&gt;This experience sparked a broader idea: using CodeFixes to systematically renew code across repositories. When legacy patterns get marked as Obsolete, manual updates can be a chore. Modernizing your solution becomes a breeze with a diagnostic and CodeFix in place&lt;/p&gt;

&lt;p&gt;This command will apply your fix across all occurrences in the solution, saving time while keeping your codebase tidy:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dotnet format --diagnostics DIAG0001&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can explore the full source here: &lt;a href="https://github.com/mikhailnefedov/CodeFixDemo" rel="noopener noreferrer"&gt;GitHub Repository: CodeFixDemo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this post got you curious about CodeFixes and analyzer tooling, check out these fantastic articles:&lt;br&gt;
&lt;a href="https://denace.dev/fixing-mistakes-with-roslyn-code-fixes" rel="noopener noreferrer"&gt;Fixing mistakes with Roslyn CodeFixes&lt;/a&gt;&lt;br&gt;
&lt;a href="https://denace.dev/testing-roslyn-analyzers-and-code-fixes" rel="noopener noreferrer"&gt;Testing Roslyn analyzers and CodeFixes&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.meziantou.net/how-to-test-a-roslyn-analyzer.htm" rel="noopener noreferrer"&gt;How to test a Roslyn analyzer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>roslyn</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Mass refactorings in .NET: a brainstorming session</title>
      <dc:creator>Mikhail Nefedov</dc:creator>
      <pubDate>Sat, 26 Apr 2025 15:05:45 +0000</pubDate>
      <link>https://dev.to/mikhailnefedov/mass-refactorings-in-net-a-brainstorming-session-3a52</link>
      <guid>https://dev.to/mikhailnefedov/mass-refactorings-in-net-a-brainstorming-session-3a52</guid>
      <description>&lt;p&gt;There are certain scenarios where adapting the code of many microservices becomes necessary. One common case is supporting a new feature. In other instances, a refactoring might be required due to a URL change affecting multiple services. Additionally, migrations such as upgrading the .NET version or switching to another log storage can demand widespread modifications.&lt;/p&gt;

&lt;p&gt;When making such changes across multiple repositories, several challenges become apparent. The work is sometimes tedious. In the best-case scenario, there is a well-defined instruction set to follow. But even then, human errors are inevitable. You may forget to adapt the configuration, overlook dependencies or misapply changes, leading to debugging sessions. Reviewing such changes may be exhausting, which could result in unnoticed mistakes being deployed. The overall effort is substantial, often causing teams to postpone these refactorings despite their necessity.&lt;/p&gt;

&lt;p&gt;To mitigate the risks and ease the developer experience, automation or at least partial automation becomes essential. Teams can streamline repetitive tasks by leveraging scripting and refactoring tools to apply changes across multiple repositories. Therefore, I want to use this blog entry to gather some ideas and questions about how such a system may look like for the .NET environment (as I'm a .NET developer myself).&lt;/p&gt;

&lt;h2&gt;
  
  
  Existing tools
&lt;/h2&gt;

&lt;p&gt;Here are two already existing tools for use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;multi-gitter&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lindell/multi-gitter" rel="noopener noreferrer"&gt;multi-gitter&lt;/a&gt; is a tool developed by lindell allowing developers to apply scripts and create merge/pull requests across many repositories. I already used it and was impressed by how much time I saved when having to tweak small things (adding a new file, config change, ...) across multiple repos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenRewrite by Moderne&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.openrewrite.org/" rel="noopener noreferrer"&gt;Openrewrite&lt;/a&gt; describes itself as a refactoring ecosystem. It allows developers to run recipes (your code changes) across multiple repositories. By utilizing a Lossless Semantic Tree and Java-based code, it is possible to refactor the code of your applications. One can run these recipes via theconsole. Moderne (the company behind OpenRewrite) also offers a cloud platform with a frontend to run the recipes&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions for .NET mass refactorings
&lt;/h2&gt;

&lt;p&gt;Several key questions arise when considering automation for mass refactorings in .NET:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can the Roslyn API be utilized for automated refactorings? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roslyn provides capabilities for analyzing and modifying C# code. Would it be possible to use it to apply C# code changes efficiently?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do we handle refactorings beyond C# code?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While we may use Roslyn for C# code, adaptations will be necessary for configuration files (.yaml, .csproj, .json, ...). How would a support for changes to these files look like? How could we combine refactorings for both c# and other files?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does an ideal developer experience look like?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Should such a tool be designed as a GUI-based application, a CLI tool or be connected to an IDE (looking at developing roslyn scripts)? What would be the most efficient solution?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is the best way to structure refactorings for easy sharing across multiple teams? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A very central question as it massively impacts developer experience. Related questions to the architectural design of the tool are: In what form should refactoring code exist? Should it be packaged as a NuGet library? Should it be stored in a repository that developers need to clone?&lt;/p&gt;

&lt;p&gt;I'm looking forward to exploring this topic in the following weeks.&lt;/p&gt;

</description>
      <category>refactoring</category>
      <category>dotnet</category>
      <category>automation</category>
    </item>
    <item>
      <title>Easing the start at a new company with glab or gh</title>
      <dc:creator>Mikhail Nefedov</dc:creator>
      <pubDate>Mon, 10 Mar 2025 18:46:38 +0000</pubDate>
      <link>https://dev.to/mikhailnefedov/easing-the-start-at-a-new-company-with-glab-or-gh-5b7m</link>
      <guid>https://dev.to/mikhailnefedov/easing-the-start-at-a-new-company-with-glab-or-gh-5b7m</guid>
      <description>&lt;h1&gt;
  
  
  Easing the start at a new company with glab or gh
&lt;/h1&gt;

&lt;p&gt;When I started at my fist software engineering job, I made my life harder than it had to be. The team already had many microservices and a lot of (NuGet) packages. They were organized neatly using GitLab's subgroups feature. This made it easier to find specific packages grouped by a domain specific usage.&lt;/p&gt;

&lt;p&gt;Unfortunately, I started to clone those packages whenever my work required updating them. I'll be honest - this led to chaos in my local file system. In the end, I had two folders (microservices and NuGet packages) containing many repositories. Sometimes the subfolders followed the structure on GitLab. In most cases, they did not. It was quite messy and disorganized.&lt;/p&gt;

&lt;p&gt;That's when I decided to clean it up and tried to find a way of organizing my folders quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  glab for gitlab
&lt;/h2&gt;

&lt;p&gt;Gitlab has an official cli tool called &lt;a href="https://docs.gitlab.com/editor_extensions/gitlab_cli/" rel="noopener noreferrer"&gt;glab&lt;/a&gt;. We can use this tool to clone all active repositories of a specific GitLab group via:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;glab repo clone -g mygitlabgroup --include-subgroups --a=false --paginate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;glab repo clone&lt;/code&gt; Command to clone a repository&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g mygitlabgroup&lt;/code&gt; Group (mygitlabgroup) from which repositories will be cloned&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--include-subgroups&lt;/code&gt; All repos within sub-groups of the group (mygitlabgroup) will be cloned&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-a=false&lt;/code&gt; Ignore archived repos&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--paginate&lt;/code&gt; All projects will be fetched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last argument is important, because from my understanding, glab uses the GitLab API. Since the underlying response is paginated, it's very possible that we would miss a lot of repositories.&lt;/p&gt;

&lt;p&gt;You can find more options for the &lt;code&gt;git repo clone&lt;/code&gt; command here: &lt;a href="https://gitlab.com/gitlab-org/cli/-/blob/main/docs/source/repo/clone.md" rel="noopener noreferrer"&gt;https://gitlab.com/gitlab-org/cli/-/blob/main/docs/source/repo/clone.md&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  gh for GitHub
&lt;/h2&gt;

&lt;p&gt;I wanted to look at another big platform that I use in my free time. GitHub does have a CLI-tool called &lt;a href="https://cli.github.com/" rel="noopener noreferrer"&gt;gh&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, there is no option to clone all repositories of an organization. You can use a bash script for cloning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!bin/bash

repos=$(gh repo list organization --no-archived --json url --limit 999 | grep -oP '"url":"\K[^"]+')

for repo in $repos; do
    echo $repo
    gh repo clone $repo
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gh repo list&lt;/code&gt; List repositories owned by user/organization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--no-archived&lt;/code&gt; Exclude archived repositories&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--json url&lt;/code&gt; Only return the repository url&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--limit 999&lt;/code&gt; Maximum count of repos in response&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;grep -oP '"url":"\K[^"]+'&lt;/code&gt; Extract url&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gh repo clone $repo&lt;/code&gt; Clone repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other solutions were discussed in this Stack Overflow thread: &lt;a href="https://stackoverflow.com/questions/46725688/how-to-clone-all-repos-including-private-repos-from-github" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/46725688/how-to-clone-all-repos-including-private-repos-from-github&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  End
&lt;/h1&gt;

&lt;p&gt;I hope this helps you get off to a faster start at your new job. It also can make the process of migrating to a new development laptop smoother by reducing the time spent on manual cloning and keeping things organized.&lt;/p&gt;

</description>
      <category>gitlab</category>
      <category>github</category>
    </item>
  </channel>
</rss>
