<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[The Dev Domain]]></title><description><![CDATA[I'm a highly driven software engineer with experience in developing performant cloud-native solutions in .NET. The Future is Written in Code.]]></description><link>https://denace.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1676059304472/-clIoEy--.png</url><title>The Dev Domain</title><link>https://denace.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 00:17:35 GMT</lastBuildDate><atom:link href="https://denace.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Using Razor Outside of Web]]></title><description><![CDATA[💡
If you want to skip my mumbling and go straight to the coding part, jump to Let's start.


Razor is a markup syntax for creating dynamic web pages in C#. It has been a part of the ASP.NET MVC framework since early 2011 and is currently a part of A...]]></description><link>https://denace.dev/using-razor-outside-of-web</link><guid isPermaLink="true">https://denace.dev/using-razor-outside-of-web</guid><category><![CDATA[razor]]></category><category><![CDATA[Blazor]]></category><category><![CDATA[C#]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[render-html]]></category><dc:creator><![CDATA[Denis Ekart]]></dc:creator><pubDate>Thu, 01 Feb 2024 23:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/p9OkL4yW3C8/upload/0aca45762bc60212f5e7e3c0e88ad269.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">If you want to skip my mumbling and go straight to the coding part, jump to <code>Let's start</code>.</div>
</div>

<p>Razor is a markup syntax for creating dynamic web pages in C#. It has been a part of the ASP.NET MVC framework since early 2011 and is currently a part of ASP.NET Core, serving as a templating engine for building web pages and components in MVC, Razor, and Blazor.</p>
<p>The primary idea behind Razor is to provide a simple syntax for generating HTML using a code-focused templating approach. The same approach can be seen in other popular frameworks and libraries focusing on HTML generation, such as React, Angular, Vue, etc. The described approach uses a hybrid of markup syntax (in this case, HTML) and a programming language (C# for Razor). It combines them during <em>rendering</em> to provide a hydrated and fully rendered web page or a page segment.</p>
<p>Let's get visual. Let's say we want to create a simple <code>&lt;div&gt;</code> with some populated data.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">b</span>&gt;</span>Name:<span class="hljs-tag">&lt;/<span class="hljs-name">b</span>&gt;</span> Denis<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">b</span>&gt;</span>Surname:<span class="hljs-tag">&lt;/<span class="hljs-name">b</span>&gt;</span> Ekart<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>The same thing could be achieved in a <code>.razor</code> component in the following way.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">b</span>&gt;</span>Name:<span class="hljs-tag">&lt;/<span class="hljs-name">b</span>&gt;</span> @Name<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">b</span>&gt;</span>Surname:<span class="hljs-tag">&lt;/<span class="hljs-name">b</span>&gt;</span> @Surname<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
}
@code {
    [Parameter]
    public string Name { get; set; } = "Denis"

    [Parameter]
    public string Surname { get; set; } = "Ekart"
}
</code></pre>
<p>The same HTML markup will be generated after the Razor templating engine renders this code. Only now we can provide the rendering engine with additional context (<code>Name</code> and <code>Surname</code> , in this case), which can render dynamic components based on the information provided. Now, scale this approach to the entire website, and you know <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-8.0">how Razor works</a> (in a nutshell 🥜).</p>
<p>Rendering web pages is cool and all, but this is not what we're here to do today. We want to (ab)use the Razor rendering engine to render HTML inside a console application.</p>
<p>Why? Well, first of all, because since .NET 8 onwards, we can. But more seriously, there are various use cases where rendering HTML can be used and useful outside of an HTTP request. Generating static website content, structuring rich email messages, and generating PDFs are just a few worthy mentions where templating comes in handy.</p>
<p>Since I've been dealing with a clunky HTML snippet, any time I send out a newsletter for our developer user group, let's convert that into a Razor template (and make my life a little easier).</p>
<h3 id="heading-lets-start">Let's start</h3>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">You can find all the code related to this article in <a target="_blank" href="https://github.com/denisekart/dotnet-snacks-samples/tree/main/src/s01-razor-template-rendering">this repository</a>.</div>
</div>

<p>We are going to create an empty console application that's targeting .net8.</p>
<p><code>dotnet new console -n ConsoleRazorRenderer</code></p>
<p>If you have <a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0">.net8 installed</a>, the created <code>ConsoleRazorRenderer.csproj</code> will look something like this.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">OutputType</span>&gt;</span>Exe<span class="hljs-tag">&lt;/<span class="hljs-name">OutputType</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net8.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
        ...
    <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>Great. Let's modify our project to allow us to include razor files. We will first need to change the project SDK to import the Razor-specific toolset. Open up the <code>ConsoleRazorRenderer.csproj</code> project file and change the <code>&lt;Project Sdk="Microsoft.NET.Sdk"&gt;</code> to <code>Microsoft.NET.Sdk.Razor</code>.</p>
<p>We will also need a couple of external dependencies.</p>
<p><code>dotnet add package Microsoft.AspNetCore.Components.Web</code> will add the services needed to render the Razor components in our console application.</p>
<p>Additionally, we require <code>dotnet add package Microsoft.Extensions.Logging</code> since the rendering engine depends on the types defined in this package.</p>
<p>Our project file should now look like this.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk.Razor"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net8.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
        ...
    <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.AspNetCore.Components.Web"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.1"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Extensions.Logging"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"8.0.0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>Okay, It's smooth sailing from here. Let's add our <code>NewsletterComponent.razor</code> file to the project and fill it up with the newsletter HTML we've been using so far. Feel free to look at the HTML in the repository, but to get a general idea, this is what the final product should look like.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707125725027/c050f44a-10ac-49cf-ba02-78b6bc896097.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>I'm really sorry for my mad drawing skillz.</p>
</blockquote>
<p>We can Identify three variables that will change in any future newsletter version. The title, attached graphics, and the actual content of the newsletter. We can make these dynamic by parametrizing our components.</p>
<pre><code class="lang-csharp">@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? EventTitle { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? HeadingImageUrl { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>The interesting thing here is the actual content of the newsletter. It makes sense that the content of a newsletter will require rich text features (you know, emojis ✨, formatting, and other appealing features). Let's allow the component to accept any arbitrary HTML snippet as the <code>Content</code>.</p>
<p>Obviously, the renderer will not allow us to inject any arbitrary string and render it as HTML without escaping it. This is where the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.markupstring?view=aspnetcore-8.0"><code>MarkupString</code></a> comes in handy.</p>
<pre><code class="lang-csharp">@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> MarkupString? Content { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>It allows us to inject raw HTML markup into our components.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Injecting unsanitized HTML can make your code insecure and lead to <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/security/cross-site-scripting?view=aspnetcore-8.0">XSS attacks</a>. Be mindful when injecting user input into your pages, regardless of where and how they will be used.</div>
</div>

<p>If we translate everything above into Razor, we can generate a component (or multiple components) that will look something like the following.</p>
<p>The <em>wrapping</em> component <code>NewsletterComponent.razor</code> will be used as a top-level component containing our styling, content, and graphics.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">HTML</span>
<span class="hljs-meta-keyword">PUBLIC</span> <span class="hljs-meta-string">"-//W3C//DTD XHTML 1.0 Transitional //EN"</span> <span class="hljs-meta-string">"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/1999/xhtml"</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"Content-Type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"text/html; charset=UTF-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>SloDug Newsletter | @Content?.Title<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">style</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span>&gt;</span>
            ...
        <span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            ...
            <span class="hljs-tag">&lt;<span class="hljs-name">NewsletterContentComponent</span> <span class="hljs-attr">Content</span>=<span class="hljs-string">"@Content?.Content"</span> <span class="hljs-attr">EventTitle</span>=<span class="hljs-string">"@Content?.Title"</span> <span class="hljs-attr">HeadingImageUrl</span>=<span class="hljs-string">"@Content?.HeadingImageUrl"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            ...        
            <span class="hljs-tag">&lt;<span class="hljs-name">NewsletterTrailerComponent</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>

@code {
    public record NewsletterDto(string? Title, MarkupString? Content, string? HeadingImageUrl);

    [Parameter]
    public NewsletterDto? Content { get; set; }
}
</code></pre>
<p><code>NewsletterContentComponent.razor</code> and <code>NewsletterTrailerComponent.razor</code> will hold dynamic content that will be rendered. While <code>NewsletterTrailerComponent</code> will wrap the static footer of our newsletter (not interesting), the <code>NewsletterContentComponent</code> will be used to format our dynamic content into HTML.</p>
<p>While there are numerous ways to structure the component hierarchy for any content, that's not the fun part of the article. Let's see how we can render our creation.</p>
<p>We will use the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.htmlrenderer?view=aspnetcore-8.0"><code>HtmlRenderer</code></a> to output the generated HTML as a string.</p>
<p>Let's first define a rendering service, which will take the <code>NewsletterDto</code> we defined earlier and produce a rendered HTML string.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">NewsletterRenderer</span>(<span class="hljs-params">HtmlRenderer renderer</span>)</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">RenderHtml</span>(<span class="hljs-params">NewsletterComponent.NewsletterDto content</span>)</span>
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> renderer.Dispatcher.InvokeAsync(<span class="hljs-keyword">async</span> () =&gt;
        {
            <span class="hljs-keyword">var</span> parameters = ParameterView.FromDictionary(<span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">object</span>?&gt;
            {
                { <span class="hljs-string">"Content"</span>, content }
            });
            <span class="hljs-keyword">var</span> output = <span class="hljs-keyword">await</span> renderer.RenderComponentAsync&lt;NewsletterComponent&gt;(parameters);

            <span class="hljs-keyword">return</span> output.ToHtmlString();
        });

        <span class="hljs-keyword">return</span> result;
    }
}
</code></pre>
<p>The service will receive a <code>HtmlRenderer</code> injected into the constructor (<a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/primary-constructors">check out C# 12 primary constructors</a>). We invoke the renderer using its dispatcher to render the <code>NewsletterComponent</code>. All we need now is to supply the renderer with the dynamic content, so we will construct a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.parameterview?view=aspnetcore-8.0"><code>ParameterView</code></a> from the content the component expects - the <code>"Content"</code> parameter expects an object of type <code>NewsletterDto</code>.</p>
<p>The <code>RenderComponentAsync</code> method call will return a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.web.htmlrendering.htmlrootcomponent?view=aspnetcore-8.0"><code>HtmlRootComponent</code></a> , which contains the rendering result and has a handy <code>ToHtmlString</code> method. Now that we have our HTML output, we need to return it to the caller.</p>
<p>It's that simple. Now, let's put it all together. In the <code>Program.cs</code> entrypoint, we will create a <code>ServiceCollection</code>, add the necessary services, and build our DI container.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> provider = <span class="hljs-keyword">new</span> ServiceCollection()
    .AddLogging()
    .AddTransient&lt;HtmlRenderer&gt;()
    .AddTransient&lt;NewsletterRenderer&gt;()
    .BuildServiceProvider();
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Alternatively, we could start off with a generic host from the <code>Microsoft.Extensions.Hosting</code> NuGet package and supply the needed services to the host builder.</div>
</div>

<p>Now that we have our service provider, we can resolve the renderer by calling</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> renderer = provider.GetRequiredService&lt;NewsletterRenderer&gt;();
</code></pre>
<p>The only thing left is to define our content and render the HTML.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> content = <span class="hljs-keyword">new</span> NewsletterComponent.NewsletterDto(
    Title: <span class="hljs-string">"Pre NTK - DevOps Edition"</span>,
    <span class="hljs-comment">// language=html</span>
    Content: <span class="hljs-keyword">new</span>(<span class="hljs-string">""</span><span class="hljs-string">"
                 (...)
                 &lt;div&gt;
                     🗓️ WHEN: Wednesday, January 17th 2024, at 6PM &lt;br /&gt;
                     📍 WHERE: BTC City Ljubljana &lt;br /&gt;
                     🔗 &lt;a ...&gt;Sign up here&lt;/a&gt; &lt;br /&gt;
                 &lt;/div&gt;
                 (...)
                 "</span><span class="hljs-string">""</span>),
    HeadingImageUrl: <span class="hljs-string">"https://slodug.blob.core.windows.net/uploads/a9424857-0326-49a4-bbb0-b89ecb9a1038/SloDug_wide_empty.png"</span>);

<span class="hljs-keyword">var</span> newsletterHtml = <span class="hljs-keyword">await</span> renderer.RenderHtml(content);
<span class="hljs-comment">// send the newsletter to subscribers (or something)</span>
</code></pre>
<p>And that's it. We can now render dynamic content using the Razor rendering engine. This also allows us to reuse existing components we use throughout our web application, making things even easier.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707166820793/e3e18990-2510-44f3-af44-e8adffa2c65f.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">👂</div>
<div data-node-type="callout-text">This is not a promo. It's just me trying to solve a fun problem and showcase the solution.</div>
</div>

<p>Newsletter sent!</p>
<p>Don't forget to check out <a target="_blank" href="https://github.com/denisekart/dotnet-snacks-samples/tree/main/src/s01-razor-template-rendering">the repository</a> for a complete sample.</p>
<p>✌️</p>
]]></content:encoded></item><item><title><![CDATA[Testing Roslyn Analyzers and Code Fixes]]></title><description><![CDATA[In the previous article, we implemented a Roslyn analyzer and code fix. This article will focus on various ways to properly test them. We will leverage existing test libraries and explore the means to write one ourselves using Roslyn workspaces. This...]]></description><link>https://denace.dev/testing-roslyn-analyzers-and-code-fixes</link><guid isPermaLink="true">https://denace.dev/testing-roslyn-analyzers-and-code-fixes</guid><category><![CDATA[roslyn]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Metaprogramming ]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Denis Ekart]]></dc:creator><pubDate>Wed, 01 Mar 2023 06:25:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677095073165/f158179f-5e0c-4f68-bcb4-3834d852b67e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://denace.dev/fixing-mistakes-with-roslyn-code-fixes">previous article</a>, we implemented a Roslyn analyzer and code fix. This article will focus on various ways to properly test them. We will leverage existing test libraries and explore the means to write one ourselves using Roslyn workspaces. This will ensure our Roslyn components behave correctly and speed up our future development efforts.</p>
<hr />
<h2 id="heading-the-story-so-far">The story so far</h2>
<p>We have been calling ourselves self-proclaimed compiler development experts for a few weeks now. During this time, we managed to</p>
<ul>
<li><p>alienate our colleagues by creating this <em>really cool</em> analyzer that will completely block any and all development efforts if multiple subsequent empty lines are encountered in our codebase,</p>
</li>
<li><p>enrage our boss by failing all of our builds and effectively shutting down our product development department, and</p>
</li>
<li><p>making a savior move by providing the ability to automatically fix all errors caused by the original analyzer.</p>
</li>
</ul>
<p>And here we are. Our mouse is hovering over the <code>Merge</code> button. We are seconds away from either becoming a hero to our company or being sued by the legal department. Suddenly we hear a voice in our head. A long-dead Greek philosopher is speaking to us.</p>
<blockquote>
<p>Quality is not an act. It is a habit.</p>
</blockquote>
<p>Oh man, our psychologist warned us about this.</p>
<p>We slowly move the pointer away from the button and take a second to regroup. We make a decision then and there. From now on, we will <strong>test before we ship!</strong></p>
<h2 id="heading-why-test-at-all">Why test at all?</h2>
<p>I will not spend too much time defending the various reasons why testing is beneficial in software development. A list of reasons (<em>pros</em>, if you will) usually includes</p>
<ul>
<li><p>ensuring the <strong>quality</strong> of the delivered product,</p>
</li>
<li><p>reducing <strong>flaws</strong> and unexpected behavior in the software,</p>
</li>
<li><p>making development easier and especially <strong>faster</strong>,</p>
</li>
<li><p>ensuring the <strong>stability</strong> of the codebase and allowing developers to refactor code safely (or at least safer), and</p>
</li>
<li><p>serving as <strong>living documentation</strong>, the kind that doesn't get outdated over time (at least in cases where tests are used as <a target="_blank" href="https://linearb.io/blog/quality-gates/">quality gates</a>).</p>
</li>
</ul>
<p>There are, of course, numerous resources available to you on the Wild Wild Web. For starters, navigate to the Microsoft Learn platform and find articles like <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/test/unit-test-basics?view=vs-2022">this</a> or <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices">that</a>.</p>
<h2 id="heading-testing-roslyn">Testing Roslyn</h2>
<p>I could have skipped the previous chapter and written a general article about testing instead. The thing is, Roslyn is a bit different. Roslyn is a compiler for all intents and purposes (well, for our purpose, at least). And you typically need a compiler to compile your code before you can test it. But we are testing the actions performed by the compiler. See the issue?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677322295702/874f1b22-3d39-4b52-8f5c-d8d31206356e.png" alt class="image--center mx-auto" /></p>
<p>Okay, I'm exaggerating. Lucky for us, <em>our</em> compiler platform is entirely written C#. It is also open, extensible, and accessible enough to think of it as just <em>any</em> other .NET library. That being said, we need to be aware of some new concepts that testing a compiler will introduce.</p>
<p>Analyzers, code fixes, and other Roslyn extensions are typically encountered in <strong>projects</strong>. These reside in <strong>solutions</strong> open in various <strong>workspaces</strong> such as your Visual Studio IDE, JetBrains Rider, VS Code, or even the dotnet CLI. Finally, the actual diagnostic messages usually relate to a particular <strong>document</strong> in your project, such as the <code>Program.cs</code> application entry point.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677325369193/cef2c7d1-b7d3-4de4-940c-515104b9e6bb.gif" alt class="image--center mx-auto" /></p>
<p>To effectively test the compiler, we need to recreate all of these components, states, and actions for each of our unit tests.</p>
<h2 id="heading-starting-from-scratch">Starting from scratch</h2>
<p>Before we start, I want to assure you there are already several libraries and tools that can get you started with unit testing. If you are just looking for that initial push, jump to the next chapter where we go over the existing libraries used for testing.</p>
<p>If you are curious to learn how Roslyn works <em>under the covers</em>, this section will focus on building a straightforward and transparent means of testing Roslyn from the ground up.</p>
<p>Writing unit tests for our particular use case should be the same as what we usually do (and we do write unit tests, right?!).</p>
<p>Let's open up the analyzer solution we implemented in the previous articles and add a test project.</p>
<pre><code class="lang-bash"> dotnet new nunit -f net7.0 -n RoslynTests
</code></pre>
<p>Head over to our `RoslynTests.csproj` project file next and add a reference to our analyzer and code fix project. The project file should now look something like this.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net7.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Nullable</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">Nullable</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">IsPackable</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">IsPackable</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ProjectReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"..\EmptyLinesAnalyzerAndCodeFix\EmptyLinesAnalyzerAndCodeFix.csproj"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.NET.Test.Sdk"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"17.3.2"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"NUnit"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"3.13.3"</span> /&gt;</span>
    <span class="hljs-comment">&lt;!-- ... --&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>We have an <a target="_blank" href="https://nunit.org/">NUnit</a> testing project ready to run. Of course, you could easily use some other testing framework, such as <a target="_blank" href="https://xunit.net/">XUnit</a> or <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest">MSTest</a>.</p>
<blockquote>
<p>All actions performed in this article are done using the CLI or directly implemented in the code. Depending on which IDE you use, there may be a more convenient way of achieving the same things. The CLI approach, at least, will guarantee that the actions performed will work regardless of the platform or IDE being used.</p>
</blockquote>
<p>Next, we need a NuGet package that will enable us to build virtual solutions. And, of course, let's remember our trusty <a target="_blank" href="https://fluentassertions.com/">FluentAssertions</a> package. Run the following scripts.</p>
<pre><code class="lang-bash">dotnet add RoslynTests package Microsoft.CodeAnalysis.CSharp.Features
dotnet add RoslynTests package FluentAssertions
</code></pre>
<p>Essentially, we are writing unit tests for how the compiler treats our source code. Let's start with wrapping some code in the <code>SourceText</code> abstraction available from <code>Microsoft.CodeAnalysis.Text</code> namespace.</p>
<pre><code class="lang-bash">var <span class="hljs-built_in">source</span> = SourceText.From(<span class="hljs-string">""</span><span class="hljs-string">"
            namespace DemoConsoleApp;


            /// &lt;summary&gt; &lt;/summary&gt;            
            public class EmptyLines
            {
            }
            "</span><span class="hljs-string">""</span>);
</code></pre>
<blockquote>
<p>For our benefit, we are using the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#raw-string-literals">raw string literal</a> notation introduced with C# 11</p>
</blockquote>
<p>Okay, so the <code>source</code> construct will be the unit we are testing. The first thing we need is to create a workspace.</p>
<h3 id="heading-create-a-virtual-workspace">Create a virtual workspace</h3>
<p>As we learned in the <a target="_blank" href="https://denace.dev/exploring-roslyn-net-compiler-platform-sdk">first article</a> of the series, the workspaces API acts as an entry point into our application. It exists to help us organize all the information about our source code. It ties everything into a neatly packed object model. It offers us direct access to the compiler layer and all the syntax and semantic information the compiler has. To put these words into a picture,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677337722338/af4fb232-627e-4794-888b-45cfa577b55d.png" alt class="image--center mx-auto" /></p>
<p>this is what we need to build. Only the entire workspace must exist for the short duration of running a single unit test.</p>
<p>When you see a solution like the one pictured above, it is usually built from a <a target="_blank" href="https://github.com/dotnet/roslyn/blob/main/src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs"><code>MSBuildWorkspace</code></a> or one of its siblings such as <a target="_blank" href="https://github.com/dotnet/roslyn/blob/main/src/VisualStudio/Core/Impl/RoslynVisualStudioWorkspace.cs"><code>VisualStudioWorkspace</code></a> . While the used workspace type is generally specific to the workload or your development environment, these workspaces usually have at least one thing in common. They allow you, the developer, to point to a folder, a <code>csproj</code>, or a <code>sln</code> file and automatically load the entire workspace for you.</p>
<p>There is, however, another workspace <code>AdhocWorkspace</code> available in the NuGet package we installed just moments before. To quote the authors, it is <em>"a workspace that allows full manipulation of projects and documents but does not persist changes"</em>. Perfect. This is precisely what we need to build our virtual workspace.</p>
<p>Let's start by creating a <code>static</code> class named <code>RoslynTestExtensions</code> and, in it, an extension method that will be responsible for creating the workspace. We are modeling a solution that will contain a single project. That project will include a single document, our <code>source</code>.</p>
<p>We will also need to provide our solution with some references. These allow us to access types and functionalities from external assemblies. Referencing <code>System</code> and <code>System.Linq</code> will do just fine for our use case.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> MetadataReference[] CommonReferences =
{
    MetadataReference.CreateFromFile(<span class="hljs-keyword">typeof</span>(<span class="hljs-keyword">object</span>).Assembly.Location),
    MetadataReference.CreateFromFile(<span class="hljs-keyword">typeof</span>(Enumerable).Assembly.Location),
};
</code></pre>
<p>Let's also define a couple of default values. We know our solution will always contain a single project. This project will also have a single document containing our syntax.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> DefaultProjectName = <span class="hljs-string">"ProjectUnderTest.csproj"</span>;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> DefaultDocumentName = <span class="hljs-string">"SourceUnderTest.cs"</span>;
</code></pre>
<p>Now we have enough information to compile a simple workspace.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> AdhocWorkspace <span class="hljs-title">CreateWorkspace</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> SourceText source</span>)</span>
{
    <span class="hljs-keyword">var</span> projectId = ProjectId.CreateNewId();
    <span class="hljs-keyword">var</span> documentId = DocumentId.CreateNewId(projectId, DefaultDocumentName);

    <span class="hljs-keyword">var</span> sourceTextLoader = TextLoader.From(
        TextAndVersion.Create(source, VersionStamp.Create()));
    <span class="hljs-keyword">var</span> document = DocumentInfo
        .Create(documentId, DefaultDocumentName)
        .WithTextLoader(sourceTextLoader);

    <span class="hljs-keyword">var</span> project = ProjectInfo.Create(
        id: projectId,
        version: VersionStamp.Create(),
        name: DefaultProjectName,
        assemblyName: DefaultProjectName,
        language: LanguageNames.CSharp)
        .WithCompilationOptions(<span class="hljs-keyword">new</span> CSharpCompilationOptions(
            OutputKind.DynamicallyLinkedLibrary));

    <span class="hljs-keyword">var</span> workspace = <span class="hljs-keyword">new</span> AdhocWorkspace();
    <span class="hljs-keyword">var</span> updatedSolution = workspace
        .CurrentSolution
        .AddProject(project)
        .AddMetadataReferences(projectId, CommonReferences)
        .AddDocument(document);

    workspace.TryApplyChanges(updatedSolution);

    <span class="hljs-keyword">return</span> workspace;
}
</code></pre>
<p>A couple of things to note here.</p>
<ul>
<li><p><strong><em>one:</em></strong> Our project will be compiled as a dynamically linked library. This allows us to not worry about the compilation failing when our <code>source</code> does not contain a valid <code>static void Main(...)</code> application entry point.</p>
</li>
<li><p><strong><em>and two:</em></strong> If you look closely at the code, you can also notice that our <code>source</code> code, <code>document</code> <code>project</code>, and <code>solution</code> appear immutable. Looking back to what we discussed in the first article of the series, most of the basic Roslyn building blocks are, in fact, immutable. With one exception. A workspace provides access to all of the abovementioned building blocks. It is designed to change over time by supporting live interactions from the environment or via a call to the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.workspace.tryapplychanges?view=roslyn-dotnet-4.3.0#microsoft-codeanalysis-workspace-tryapplychanges(microsoft-codeanalysis-solution)"><code>workspace.TryApplyChanges(updatedSolution)</code></a>.</p>
</li>
</ul>
<h3 id="heading-find-the-diagnostic">Find the diagnostic</h3>
<p>Now that we have a solution, we can manipulate it and attempt to produce an analyzer diagnostic we are trying to test. Starting from the <code>source</code> text, we can create another extension method.</p>
<p>The extension method will return a collection of diagnostics in the <code>source</code> code. More specifically, only the diagnostics that the analyzer <code>TAnalyzer</code> is reporting. Using the standard Roslyn APIs, that is quite easy to achieve.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title">Task</span>&lt;<span class="hljs-title">ImmutableArray</span>&lt;<span class="hljs-title">Diagnostic</span>&gt;&gt; <span class="hljs-title">GetDiagnostics</span>&lt;<span class="hljs-title">TAnalyzer</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> SourceText source</span>) <span class="hljs-keyword">where</span> TAnalyzer : DiagnosticAnalyzer, <span class="hljs-title">new</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> workspace = source.CreateWorkspace();
    <span class="hljs-keyword">var</span> document = <span class="hljs-comment">/*the default document*/</span>
    <span class="hljs-keyword">var</span> analyzer = <span class="hljs-keyword">new</span> TAnalyzer();
    <span class="hljs-keyword">var</span> diagnosticDescriptor = analyzer.SupportedDiagnostics.Single();
    <span class="hljs-keyword">var</span> compilation = <span class="hljs-keyword">await</span> <span class="hljs-comment">/*the default project*/</span>.GetCompilationWithAnalyzerAsync(analyzer);
    <span class="hljs-keyword">var</span> allDiagnostics = <span class="hljs-keyword">await</span> compilation.GetAllDiagnosticsAsync();

    <span class="hljs-keyword">return</span> allDiagnostics.Where(x =&gt; 
            x.Id == diagnosticDescriptor.Id &amp;&amp;
            x.Location.SourceTree?.FilePath == document.Name)
        .ToImmutableArray();
}
</code></pre>
<blockquote>
<p>I will let you fill in the <em>blanks</em>. Feel free to check the <code>roslyn-playground</code> repository (<em>link at the end of this article</em>) if you get stuck anywhere.</p>
</blockquote>
<p>We can now finally create a simple unit test to wrap everything up.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">EmptyLinesAnalyzer_ShouldReportDiagnostic_WhenMultipleEmptyLinesExist</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// Arrange</span>
    <span class="hljs-keyword">var</span> source = <span class="hljs-comment">/*we know this already*/</span>

    <span class="hljs-comment">// Act</span>
    <span class="hljs-keyword">var</span> actual = <span class="hljs-keyword">await</span> source.GetDiagnostics&lt;EmptyLinesAnalyzer&gt;();

    <span class="hljs-comment">// Assert</span>
    actual.ShouldContainDiagnosticWithId(<span class="hljs-string">"DM0001"</span>);
}
</code></pre>
<p>Ahh, one bit is missing. Let's create another extension method <code>ShouldContainDiagnosticWithId</code> . It should receive an <code>ImmutableArray</code> of <code>Diagnostic</code>s, and properly assert the existence of a diagnostic with a provided <code>Id</code>.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ShouldContainDiagnosticWithId</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> ImmutableArray&lt;Diagnostic&gt; diagnostics, <span class="hljs-keyword">string</span> diagnosticId</span>)</span>
{
    diagnostics.Should().NotBeNull().And.HaveCountGreaterOrEqualTo(<span class="hljs-number">1</span>);
    diagnostics.Should().Contain(diagnostic =&gt; diagnostic.Id == diagnosticId);
}
</code></pre>
<p>✅ Let's move on!</p>
<h3 id="heading-apply-the-code-fix">Apply the code fix</h3>
<p>We can use a similar approach for testing our code fix. Since we know our diagnostic contains an accompanying code fix, it gives us a perfect place to start modifying the virtual solution.</p>
<p>Similarly to what we did when testing the diagnostic in the previous chapter, we need to find the diagnostic to fix. Let's assume we are left with a <code>singleDiagnostic</code> that was reported by the analyzer under test. Because we checked that the diagnostic has the correct id, we also know that our <code>TCodeFixProvider</code> supports a code action to fix it.</p>
<p>I suppose we are making way too many assumptions at this point. Naturally, our code will have proper validation in place so that nothing will be left to chance 😉.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> <span class="hljs-title">Task</span>&lt;<span class="hljs-title">SourceText</span>&gt; <span class="hljs-title">ApplyCodeFixes</span>&lt;<span class="hljs-title">TAnalyzer</span>, <span class="hljs-title">TCodeFixProvider</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> SourceText source</span>) 
<span class="hljs-keyword">where</span> TAnalyzer : DiagnosticAnalyzer, <span class="hljs-title">new</span>(<span class="hljs-params"></span>) 
<span class="hljs-keyword">where</span> TCodeFixProvider : CodeFixProvider, <span class="hljs-title">new</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// create the workspace, find the diagnostic, and ...</span>
    <span class="hljs-keyword">await</span> workspace.ApplyCodeFix&lt;TCodeFixProvider&gt;(document, singleDiagnostic);

     <span class="hljs-keyword">return</span> <span class="hljs-comment">/*the modified source*/</span>
}
</code></pre>
<p>The only thing left to do is to implement the <code>ApplyCodeFix&lt;TCodeFixProvider&gt;</code> extension method.</p>
<p>If you remember <a target="_blank" href="https://denace.dev/fixing-mistakes-with-roslyn-code-fixes">the article</a> where we implemented the code fix provider, we needed to implement the <code>CodeFixProvider</code> abstract class. As a part of that implementation, we implemented the <code>RegisterCodeFixesAsync(CodeFixContext)</code> method. This will be our access point to the code fix provider.</p>
<p>We can surely create an instance of the <code>CodeFixContext</code> since we have all the needed building blocks. A point of interest for us is the <code>registerCodeFix</code> argument. This delegate gets invoked whenever the code fix encounters a fixable diagnostic. As a result, we are left with a <code>CodeAction</code>.</p>
<p>This is something we are already familiar with. Remember, a code action contains all the information needed to apply the fix to our solution. This is achieved through the set of operations it exposes. Calling <code>codeAction.GetOperationsAsync</code> will enable us to access the <code>ApplyChangesOperation</code>, which will contain a <code>ChangedSolution</code> . This is the fix we need. The only thing left is to apply that changed solution to our workspace.</p>
<p>💡<em>I, for one, love it when things start falling in</em>to <em>place.</em></p>
<p>Let's type this up real quick.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">ApplyCodeFix</span>&lt;<span class="hljs-title">TCodeFixProvider</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> AdhocWorkspace workspace, Document document, Diagnostic singleDiagnostic</span>)
<span class="hljs-keyword">where</span> TCodeFixProvider : CodeFixProvider, <span class="hljs-title">new</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> codeFixProvider = <span class="hljs-keyword">new</span> TCodeFixProvider();
    List&lt;CodeAction&gt; actions = <span class="hljs-keyword">new</span>();
    <span class="hljs-keyword">var</span> context = <span class="hljs-keyword">new</span> CodeFixContext(document,
        singleDiagnostic,
        (a, _) =&gt; actions.Add(a),
        CancellationToken.None);
    <span class="hljs-keyword">await</span> codeFixProvider.RegisterCodeFixesAsync(context);
    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> codeAction <span class="hljs-keyword">in</span> actions)
    {
        <span class="hljs-keyword">var</span> operations = <span class="hljs-keyword">await</span> codeAction.GetOperationsAsync(CancellationToken.None);
        <span class="hljs-keyword">if</span> (operations.IsDefaultOrEmpty)
        {
            <span class="hljs-keyword">continue</span>;
        }

        <span class="hljs-keyword">var</span> changedSolution = operations.OfType&lt;ApplyChangesOperation&gt;().Single().ChangedSolution;
        workspace.TryApplyChanges(changedSolution);
    }
}
</code></pre>
<p>Again, we have everything we need in place to finally write the unit test. The following snippet is self-explanatory.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">EmptyLinesCodeFix_ShouldApplyFix_WhenMultipleEmptyLinesExist</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// Arrange</span>
    <span class="hljs-keyword">var</span> source = SourceText.From(<span class="hljs-string">""</span><span class="hljs-string">"
        namespace DemoConsoleApp;


        public class EmptyLines
        { }
        "</span><span class="hljs-string">""</span>);
    <span class="hljs-keyword">var</span> expected = SourceText.From(<span class="hljs-string">""</span><span class="hljs-string">"
        namespace DemoConsoleApp;

        public class EmptyLines
        { }
        "</span><span class="hljs-string">""</span>);

    <span class="hljs-comment">// Act</span>
    <span class="hljs-keyword">var</span> actual = <span class="hljs-keyword">await</span> source.ApplyCodeFixes&lt;EmptyLinesAnalyzer, EmptyLinesCodeFix&gt;();

    <span class="hljs-comment">// Assert</span>
    actual.ShouldBeEqualTo(expected);
}
</code></pre>
<p>✅ Pass. We are done.</p>
<p>Well, not quite. As mentioned, the approach we just implemented was meant to demonstrate how Roslyn works and should not be considered a valid approach to test your analyzers and code fixes (please). Let's look at how testing should actually be attempted.</p>
<h2 id="heading-using-roslyns-testing-library">Using Roslyn's testing library</h2>
<p>Luckily for us, we can skip the previous chapter altogether. The team behind Roslyn provides a comprehensive suite of utilities designed to test analyzers, code fixes, and other components.</p>
<p>Getting started here is easy. Make sure you install the library first.</p>
<pre><code class="lang-bash">dotnet add RoslynTests package Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit
</code></pre>
<p>Keep in mind that similar packages also exist for <a target="_blank" href="https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit">XUnit</a> and <a target="_blank" href="https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.MSTest">MSTest</a>.</p>
<p>The only thing we are left with is to use a <em>verifier</em> for the code fix we want to test. We can be tricky here and create an <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#using-alias">alias</a> for the verifier.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> EmptyLinesAnalyzerAndFix;
<span class="hljs-keyword">using</span> Microsoft.CodeAnalysis.Text;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">RoslynTests</span>;

<span class="hljs-keyword">using</span> Verify = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.CodeFixVerifier&lt;EmptyLinesAnalyzer, EmptyLinesCodeFix&gt;;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmptyLinesTests</span>
{
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>The previous test can now be rewritten to use the <code>CodeFixVerifier</code> like so.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">EmptyLinesCodeFix_ShouldApplyFix_WhenMultipleEmptyLinesExist</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// Arrange</span>
    <span class="hljs-keyword">var</span> source = <span class="hljs-comment">/*...*/</span>
    <span class="hljs-keyword">var</span> expected = <span class="hljs-comment">/*...*/</span>
    <span class="hljs-keyword">var</span> diagnostic = Verify.Diagnostic()
        .WithSpan(startLine: <span class="hljs-number">2</span>, startColumn: <span class="hljs-number">1</span>, endLine: <span class="hljs-number">4</span>, endColumn: <span class="hljs-number">1</span>)
        .WithSpan(startLine: <span class="hljs-number">4</span>, startColumn: <span class="hljs-number">1</span>, endLine: <span class="hljs-number">4</span>, endColumn: <span class="hljs-number">7</span>);

    <span class="hljs-comment">// Assert</span>
    <span class="hljs-keyword">await</span> Verify.VerifyCodeFixAsync(source, diagnostic, expected);
}
</code></pre>
<p>Two things to note. The <code>Verify</code> alias already <em>knows</em> the diagnostic we are testing. The only thing we are left with is to define where the diagnostic is supposed to occur. The <code>WithSpan</code> extension, along with <a target="_blank" href="https://github.com/dotnet/roslyn-sdk/blob/main/src/Microsoft.CodeAnalysis.Testing/README.md#verifier-overview">numerous other extensions</a>, can be used to specify the constraints of the diagnostic we are testing.</p>
<p>That's all good, but aren't we supposed to get a single diagnostic in this test? Why are we specifying two locations, then?</p>
<p>Looking back at <a target="_blank" href="https://denace.dev/getting-started-with-roslyn-analyzers">the article</a> where we implemented the analyzer, we added an <code>additionalLocation</code> that was later used by the code fix provider. This is the second span we want to test for.</p>
<p>Calling the <code>Verify.VerifyCodeFixAsync</code> will cycle over numerous verifications that will generally go over the same steps we went over in the previous chapter. Only the verifications executed here are far more precise and elaborate. The <a target="_blank" href="https://github.com/dotnet/roslyn-sdk/blob/main/src/Microsoft.CodeAnalysis.Testing/README.md">documentation</a> in the Roslyn repository is a great entry point if you wish to explore the numerous capabilities of the testing library further.</p>
<blockquote>
<p>We intentionally skipped testing the analyzer, since the library also provides the same assertions when testing an accompanying code fix provider. General guidelines and <a target="_blank" href="https://github.com/dotnet/roslyn-sdk/blob/main/src/Microsoft.CodeAnalysis.Testing/README.md#best-practices">best practices</a> for using the Roslyn test library suggest testing only the code fix provider when possible.</p>
<p>If this is not possible, you can freely use the <code>AnalyzerVerifier</code> from the <a target="_blank" href="https://www.nuget.org/packages/Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit">Analyzer NuGet package</a>, to test the analyzer on its own.</p>
</blockquote>
<p>But wait, there's more.</p>
<h3 id="heading-streamline-testing-using-the-roslyn-testing-library">Streamline testing using the Roslyn testing library</h3>
<p>There are various cool and somewhat undocumented features included in this testing library. A couple of them, at least, can significantly streamline your test development.</p>
<p>The testing library supports a special markup syntax to aid in your test development.</p>
<p>Let's take the test of our code fix provider (the one above). Instead of explicitly creating a <code>Diagnostic()</code> with expected spans, we can embed that information into the source code.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> source = <span class="hljs-string">""</span><span class="hljs-string">"
    namespace DemoConsoleApp;
    {|DM0001:

    |}public class EmptyLines
    { }
    "</span><span class="hljs-string">""</span>;
</code></pre>
<p>Notice the <code>{|DM0001: ... |}</code> markup, which tells the verifier that there is an expected diagnostic with the id <code>DM0001</code> supposed to appear in the same position as denoted by the brackets.</p>
<p>The assertion part can, therefore, also be simplified since the <code>diagnostic</code> was implicitly created for us by the library.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">await</span> Verify.VerifyCodeFixAsync(source, expected);
</code></pre>
<p>We can use similar syntax to specify the location of <em>any</em> reported diagnostic using an alternative bracket markup <code>[| ... |]</code> .</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Test</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">EmptyLinesAnalyzer_ShouldReportDiagnostic_WhenMultipleEmptyLinesExist_UsingRoslynLibraryCustomSyntax</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// Arrange</span>
    <span class="hljs-keyword">var</span> source = <span class="hljs-string">""</span><span class="hljs-string">"
        namespace DemoConsoleApp;
        [|

        |]public class EmptyLines
        { }
        "</span><span class="hljs-string">""</span>;

    <span class="hljs-comment">// Assert</span>
    <span class="hljs-keyword">await</span> Verify.VerifyAnalyzerAsync(source);
}
</code></pre>
<p>Pretty cool, right?! Now replace the above markup with a marker <code>$$</code> that specifies the expected location of a diagnostic. We have another passing test ✅.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">var</span> source = <span class="hljs-string">""</span><span class="hljs-string">"
    namespace DemoConsoleApp;
    $$

    public class EmptyLines
    { }
    "</span><span class="hljs-string">""</span>;
</code></pre>
<h2 id="heading-in-summary">In summary</h2>
<p>We managed to get our hands dirty with Roslyn's workspaces. This API gave us enough insight into code analysis in C#. Additionally, we learned how to properly and easily test our inventions.</p>
<p>If you made it this far, head over to the <a target="_blank" href="https://github.com/denisekart/exploring-roslyn"><strong>denisekart/exploring-roslyn</strong></a> repository and start exploring⭐.</p>
<hr />
<p>... As the sun is starting to rise, we glance at our updated merge request. Soothing green light from the generated code coverage report shines blissfully in our eyes. As we move our mouse closer to the <code>Merge</code> button, we wonder. <em>What other secrets lie beneath this compiler's shining API?</em></p>
<p>Until next time, ✌</p>
]]></content:encoded></item><item><title><![CDATA[Fixing Mistakes With Roslyn Code Fixes]]></title><description><![CDATA[In this part of the series, we will focus on creating a code fix provider that will fix a diagnostic we reported with empty lines analyzer, a Roslyn analyzer we implemented in the previous article.

Storytime!
A week ago, we had a brilliant idea to i...]]></description><link>https://denace.dev/fixing-mistakes-with-roslyn-code-fixes</link><guid isPermaLink="true">https://denace.dev/fixing-mistakes-with-roslyn-code-fixes</guid><category><![CDATA[roslyn]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Metaprogramming ]]></category><category><![CDATA[General Programming]]></category><dc:creator><![CDATA[Denis Ekart]]></dc:creator><pubDate>Tue, 14 Feb 2023 06:02:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676225794750/65ffcb79-9925-4d22-9884-e0639b0f226e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this part of the series, we will focus on creating a code fix provider that will fix a diagnostic we reported with <em>empty lines analyzer</em>, a Roslyn analyzer we implemented in the <a target="_blank" href="https://denace.dev/getting-started-with-roslyn-analyzers">previous article</a>.</p>
<hr />
<h4 id="heading-storytime">Storytime!</h4>
<p>A week ago, we had a brilliant idea to implement an analyzer that would fail the entire build whenever multiple empty lines were encountered in our solution. Of course, someone made you push the code we implemented into production (hey, don't look at me). It just so happens that your company, CoolCorp, has ground to a halt because of that.</p>
<p>You get called into a web meeting titled "<em>Fw: URGENT Who the hell broke our builds?!!"</em>. You enter the chat unbeknownst to the amount of trouble your Roslyn learning journey has caused the company. Your tech lead is bisecting the git history in front of senior management, trying to find the commit that broke everything. Ahh, here it is <code>feat: disallow multiple subsequent empty lines (that will teach them)</code>.</p>
<p>The commit author, you, is now known to everyone. As you unmute your mic, you angle your web camera slightly to the right to reveal the famous quote from a guy that has something to do with faces, or books, or lizards.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676225530211/2659c347-028f-420a-8be0-0f64fbe0c3d7.jpeg" alt="&quot;Move Fast and Break Things&quot; by rossbelmont is licensed under CC BY-NC-SA 2.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/2.0/?ref=openverse." class="image--center mx-auto" /></p>
<p>You speak up with confidence: <strong>I can fix it!</strong></p>
<h3 id="heading-enter-code-fixes">Enter code fixes</h3>
<p>Roslyn allows you to write a code fix provider, that can enable the IDE to calculate a change to the solution that would fix the reported diagnostic. Let's break this down.</p>
<p>An analyzer will report a diagnostic with a unique id (e.g. <code>DM0001</code>). A code fix provider can register a <em>code action</em> capable of providing a fix for the issue from that diagnostic. This means that any time there is an <em>error</em>, <em>warning</em>, or even an <em>informational</em> squiggle in your IDE, chances are there is an accompanying code fix that can make that squiggle go away.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676234599557/17d7023c-1b47-45e4-a664-6ae68bc3c2fb.gif" alt class="image--center mx-auto" /></p>
<p>The code fix provider will take the existing solution, make the necessary syntax changes, and return a new, fixed solution.</p>
<blockquote>
<p>Side note; you must follow several rules when implementing analyzers or code fixes for Roslyn. The diagnostic id needs to be unique across all analyzers and be in a specified format, must not be null, must have the correct category, ...</p>
<p>When developing a for Roslyn, there are several <a target="_blank" href="https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.md">helpful analyzers and code fixes</a> to aid in the development.</p>
</blockquote>
<h3 id="heading-writing-a-code-fix-provider">Writing a code fix provider</h3>
<p>Starting with our existing analyzer project <code>EmptyLinesAnalyzerAndCodeFix.csproj</code>, let's create a new class and call it <code>EmptyLinesCodeFix</code>.</p>
<p>Before doing anything else, we need another NuGet package to help us manipulate common workspace items such as <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.document?view=roslyn-dotnet-3.2">Documents</a>. Head over to the CLI and run the following command.</p>
<pre><code class="lang-bash">dotnet add EmptyLinesAnalyzerAndCodeFix package Microsoft.CodeAnalysis.Workspaces.Common
</code></pre>
<p>With proper dependencies installed, the class can derive from <code>Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider</code> abstract class. For the IDE to recognize that this is a code fix provider, we must also decorate it with an <code>ExportCodeFixProviderAttribute</code>. This is what a barebones code fix provider should look like.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> System.Collections.Immutable;
<span class="hljs-keyword">using</span> System.Composition;
<span class="hljs-keyword">using</span> System.Threading.Tasks;
<span class="hljs-keyword">using</span> Microsoft.CodeAnalysis;
<span class="hljs-keyword">using</span> Microsoft.CodeAnalysis.CodeFixes;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">EmptyLinesAnalyzerAndFix</span>;

[<span class="hljs-meta">ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(EmptyLinesCodeFix)), Shared</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmptyLinesCodeFix</span> : <span class="hljs-title">CodeFixProvider</span>
{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> Task <span class="hljs-title">RegisterCodeFixesAsync</span>(<span class="hljs-params">CodeFixContext context</span>)</span>
    {
        <span class="hljs-comment">// ...</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> ImmutableArray&lt;<span class="hljs-keyword">string</span>&gt; FixableDiagnosticIds { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<p>If you are keen on exploring GitHub and you run across any code fix provider in the <a target="_blank" href="https://github.com/dotnet/roslyn">Roslyn repository</a>, you might notice it is also decorated by a <code>[Shared]</code> attribute. The reason for that is code fix providers are <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/framework/mef/">MEF</a> components. As per Roslyn guidelines, they should also be stateless. This means an IDE using a particular code fix provider can optimize its usage by creating a single <em>shared</em> instance of said code fix provider.</p>
<h4 id="heading-link-the-code-fix-to-the-analyzer">Link the code fix to the analyzer</h4>
<p>We first need is to provide our code fix provider with a list of diagnostics it can fix. Since we are only fixing <em>our</em> diagnostics and we also conveniently remembered to expose the <code>DiagnosticId</code> constant in the <code>EmptyLinesAnalyzer</code>, we can use it here.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> ImmutableArray&lt;<span class="hljs-keyword">string</span>&gt; FixableDiagnosticIds =&gt; ImmutableArray.Create(EmptyLinesAnalyzer.DiagnosticId);
</code></pre>
<p>This will ensure that any time the referenced diagnostic is encountered, this code fix provider will be able to fix it.</p>
<p>To be fair, our implementation can't fix anything yet. For this to happen, we also need to register a <em>code action. A code action describes an intent to change one or more documents in a solution. Simply put, a code action allows the host (e.g. an IDE), to display a light bulb</em> 💡, which enables you to apply a code fix.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676312103698/c554c612-731f-418a-ba55-c3669debb32f.gif" alt class="image--center mx-auto" /></p>
<blockquote>
<p>For JetBrains Rider users, there is an <a target="_blank" href="https://youtrack.jetbrains.com/issue/RIDER-18372/Roslyn-quick-fix-does-not-provide-an-option-to-fix-the-issue-in-file-solution">annoying <s>limitation </s> feature</a> in the IDE that allows you to apply a code fix only to a particular occurrence. Visual Studio, for example, will display several additional options for fixing your code and previewing the code fix.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676315300257/a7a3af48-327c-4895-8e56-7164d0f47bcd.gif" alt class="image--center mx-auto" /></p>
</blockquote>
<h4 id="heading-enable-fixing-multiple-diagnostics">Enable fixing multiple diagnostics</h4>
<p>For the code fix provider to be able to fix multiple occurrences simultaneously, we also need to provide a "smart" way to achieve this. Luckily, Roslyn already offers a built-in solution for fixing batches of diagnostics in a single go (just be mindful of its <a target="_blank" href="https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md#limitations-of-the-batchfixer">limitations</a>).</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> FixAllProvider <span class="hljs-title">GetFixAllProvider</span>(<span class="hljs-params"></span>)</span> =&gt; WellKnownFixAllProviders.BatchFixer;
</code></pre>
<h4 id="heading-register-the-code-fix">Register the code fix</h4>
<p>To enable the compiler to see and the IDE to display our code fix action, we need to (<em>hint hint</em>) implement the <code>RegisterCodeFixesAsync</code> method.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> Task <span class="hljs-title">RegisterCodeFixesAsync</span>(<span class="hljs-params">CodeFixContext context</span>)</span>
{
    <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> action = <span class="hljs-string">"Remove redundant empty lines"</span>;

    <span class="hljs-keyword">var</span> diagnostic = context.Diagnostics.First();
    context.RegisterCodeFix(
        CodeAction.Create(
            title: action,
            createChangedDocument: cancellationToken =&gt; <span class="hljs-comment">/*...*/</span>,
            equivalenceKey: action),
        diagnostic);

    <span class="hljs-keyword">return</span> Task.CompletedTask;
}
</code></pre>
<p>The text specified in the <code>title</code> will be displayed when the user hovers over the light bulb. "<em>Remove redundant empty lines</em>" will also be used as an <code>equivalenceKey</code>. This means that by fixing multiple occurrences of this diagnostic (say, by executing the code action over the entire solution), all diagnostics with the same equivalence key will be fixed.</p>
<p>With all that ceremony out of the way, we are left with the final piece of the puzzle. To fix the code, we need to provide a factory that will be invoked by the compiler once the change is necessary. The <code>createChangedDocument</code> parameter takes a delegate. When invoked, this delegate will receive a cancellation token provided by the compiler and return a document or solution that was modified by our code fix provider.</p>
<blockquote>
<p>The code fix provider needs to utilize the received <code>cancellationToken</code> properly. The compiler may, at any time, cancel the operation. For the IDE to remain responsive, any extensions to the compiler (e.g. our code fix provider) should react to cancellation requests immediately.</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;Document&gt; <span class="hljs-title">RemoveEmptyLines</span>(<span class="hljs-params">Document document,
    Diagnostic diagnostic,
    CancellationToken cancellationToken</span>)</span>
{
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<h4 id="heading-implement-the-code-fix-provider-logic">Implement the code fix provider logic</h4>
<p>We now have all the information available to fix our diagnostic. The <code>document</code> defines the syntax tree we will be modifying. It also represents the document where the <code>diagnostic</code> is located. The <code>cancellationToken</code>, as mentioned, should be passed to any time-consuming operations (or we could just <code>cancellationToken.ThrowIfCancellationRequested();</code> when doing any CPU-bound work in our code).</p>
<p>And now, the fun part, let's figure out how to change the document to fix the diagnostic. 👨‍💻</p>
<p>What? No. No peeking! Alright, I will include a link to the demo repository at the end of this article.</p>
<p>If you are adamant about trying to solve this yourself, here are a couple of tips to help you on the way.</p>
<ul>
<li><p>Similarly to the previous article, we only need to deal with the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis">syntax tree</a>. This means that we can skip <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/semantic-analysis">semantic analysis</a> altogether and focus on a single document source code,</p>
</li>
<li><p>referencing the analyzer we implemented in the <a target="_blank" href="https://denace.dev/getting-started-with-roslyn-analyzers">previous article</a>, we were smart enough to include an <code>additionalLocation</code> which is a good place to start looking for the part of the syntax tree that needs to change,</p>
</li>
<li><p>remember, <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis#understanding-syntax-trees">Roslyn syntax trees are immutable</a>. There are several benefits to this. Although for us, this means that any time we need to change a node in that tree, it needs to be rebuilt from the ground up.</p>
</li>
</ul>
<p>Let's see how our implemented solution works in the demo console app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1676321306123/e0942ef3-dc48-4088-9d51-a2f7087ae5e4.gif" alt class="image--center mx-auto" /></p>
<p>Great! We are done. Ship it. Now.</p>
<p>But wait, let's take a step back this time. Let's imagine not hearing Mark Zuckerberg uttering the words that decorate our office walls.</p>
<p>Let's be sane this time around and test our code instead of our boss's patience.</p>
<p>As promised, here is a link to the <a target="_blank" href="https://github.com/denisekart/exploring-roslyn"><strong>denisekart/exploring-roslyn</strong></a> repository, where you can find all the samples from this series. We will explore various ways of testing Roslyn analyzers and code fixes in the next installment.</p>
<p>Until next time, ✌</p>
]]></content:encoded></item><item><title><![CDATA[Getting Started With Roslyn Analyzers]]></title><description><![CDATA[This article will focus on the most basic setup needed to create and use a Roslyn analyzer. Starting with an empty solution, we will go through the necessary configuration to develop and utilize a Roslyn analyzer.

The last article ended with the wor...]]></description><link>https://denace.dev/getting-started-with-roslyn-analyzers</link><guid isPermaLink="true">https://denace.dev/getting-started-with-roslyn-analyzers</guid><category><![CDATA[roslyn]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Metaprogramming ]]></category><category><![CDATA[diagnostics]]></category><dc:creator><![CDATA[Denis Ekart]]></dc:creator><pubDate>Sun, 05 Feb 2023 22:27:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675286191591/63279503-a0d4-4143-a877-a94307dd6a9d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article will focus on the most basic setup needed to create and use a Roslyn analyzer. Starting with an empty solution, we will go through the necessary configuration to develop and utilize a Roslyn analyzer.</p>
<hr />
<p>The <a target="_blank" href="https://denace.dev/exploring-roslyn-net-compiler-platform-sdk">last article</a> ended with the words, "<em>let's write some code.</em>" While I'm all for writing code, perhaps some purpose is needed first. One of the goals of an open compiler platform is, for sure, the ability to extend and modify it to solve your problems. I have problems (yikes!).</p>
<h3 id="heading-first-define-the-problem">First, define the problem</h3>
<p>While this makes complete sense when written by a stranger on a blog post, it is always good to be reminded that to solve a problem, you must first know what the problem is (duh, get on with it already).</p>
<p>Let's say you get employed at a cool, trending company with a product wholly written in the latest flavor of C#. It's your first day on the job with your brand new 14" Mac, and a colleague gives you a link to the codebase you will be working on. You Set everything up, open up your favorite code editor, and load <code>Program.cs</code> . You see this.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// this is the entrypoint to the CoolCorp application</span>



<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);




<span class="hljs-comment">// Add services to the container.</span>
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();




<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
</code></pre>
<p>And then the screen ends. There is no more vertical space on your brand-new MacBook. It has been completely eaten up by empty lines and, <em>gulp</em>, whitespace!</p>
<p>Now, just forget about the handy scrolling capabilities of that state-of-the-art haptic feedback trackpad thingy. You hate that you can only see 4 relevant lines of code on your screen. It hurts, and you want to... No, you need to fix it now!</p>
<p>The goal is to quickly turn the above monster into something more concise, clean, and structured, like so.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// this is the entrypoint to the CoolCorp application</span>

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

<span class="hljs-comment">// Add services to the container.</span>
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-comment">// Configure the HTTP request pipeline.</span>
<span class="hljs-keyword">if</span> (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler(<span class="hljs-string">"/Error"</span>);
}

app.UseStaticFiles();

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
</code></pre>
<p><strong>Bonus</strong>, you wish to punish the people writing such code, so you also want to make sure the build fails if there are multiple sequential empty lines anywhere in the solution.</p>
<p>Great, we have a problem to solve. To be entirely honest, you could just browse the Roslyn documentation and find the diagnostic with ID <code>IDE2000</code>. Then set the severity to <code>error</code> and grab a cold beverage with one of your normal non-developer friends who don't enjoy torturing themselves with coding abstractions at 3 AM on Saturday.</p>
<p>No? No! Let's dive in!</p>
<h3 id="heading-set-up-the-environment-take-one">Set up the environment - take one</h3>
<p>There are several ways to start developing Roslyn extensions. A straightforward way to start is to open up your latest Visual Studio 2022, find the <strong><em>Analyzer with Code Fix (.NET Standard)</em></strong> template, and dive into it.</p>
<p>Of course, you must ensure that you have installed the necessary workloads first (hint, hint <strong><em>Visual Studio extension development</em></strong> workload with the optional <strong><em>.NET Compiler Platform SDK</em></strong> ☑ component).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675362024104/eb1a910e-1c30-42dd-b66c-d729996bd2ec.png" alt class="image--center mx-auto" /></p>
<p>Once you create a project, you are greeted with an example analyzer and code fix, including the NuGet or a VSIX Visual Studio extension boilerplate code.</p>
<p>Project setup, as well as the means of distribution, are all essential things to consider, but we will dive into that topic at a later time. Right now, we need to get up and running ASAP. Your boss is watching, and the haptic feedback trackpad is about to give. To get up and running fast, we are going to start from scratch, literally (I know, I know... bear with me).</p>
<h3 id="heading-set-up-the-environment-take-two">Set up the environment - take two</h3>
<p>Okay, let's start with an empty solution. I'll let you figure out the best way to create it. My preference is always <code>dotnet new sln</code>.</p>
<h4 id="heading-create-a-basic-console-application">Create a basic console application</h4>
<p>While waiting on your Mac to load the entire CoolCorp solution, let's create a simple demo console app <code>dotnet new console -n DemoConsoleApp</code>. We will use this console application to quickly get feedback on our analyzer (<em>no, you shouldn't use a console application to test your work</em>).</p>
<blockquote>
<p>Side note; if you are new to <code>dotnet</code> and are still not impressed by the advances made in the last couple of years, just run <code>dotnet run --project DemoConsoleApp</code>, and you have a working console application ready to run in a single step. Mind you, the only file in the project has a single line of syntax written in it.</p>
<pre><code class="lang-plaintext">Hello, World!
</code></pre>
</blockquote>
<h4 id="heading-create-the-analyzer-project-outline">Create the analyzer project outline</h4>
<p>Create a new class library project and name it something cool like <code>NewLinesAreRelevantSyntaxTooAnalyzer.csproj</code> (or don't, please). Roslyn components should target the netstandard2.0 TFM to ensure that analyzers load in various runtimes available today (mono, .NET Framework, and .NET Core).</p>
<p>Since we are already adding new functionalities to our solution, let's reference the packages needed to create an analyzer.</p>
<pre><code class="lang-bash">dotnet new classlib -f netstandard2.0 --langVersion latest -n EmptyLinesAnalyzerAndCodeFix
dotnet add EmptyLinesAnalyzerAndCodeFix package Microsoft.CodeAnalysis.CSharp
dotnet add EmptyLinesAnalyzerAndCodeFix package Microsoft.CodeAnalysis.Analyzers
</code></pre>
<p>If everything worked out, the structure of the solution should look like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675372780805/7871063b-17e2-4e2f-82cd-4dd3447f0709.png" alt class="image--center mx-auto" /></p>
<p>There is now only a single piece of the puzzle missing. We need to reference the analyzer in our <em>DemoConsoleApp.</em></p>
<p>To do that, open the <code>DemoConsoleApp.csproj</code>, and create an <code>&lt;ItemGroup&gt;</code> that adds a project reference to the analyzer. The file should look something like this.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Project</span> <span class="hljs-attr">Sdk</span>=<span class="hljs-string">"Microsoft.NET.Sdk"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">OutputType</span>&gt;</span>Exe<span class="hljs-tag">&lt;/<span class="hljs-name">OutputType</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TargetFramework</span>&gt;</span>net7.0<span class="hljs-tag">&lt;/<span class="hljs-name">TargetFramework</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ProjectReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"..\EmptyLinesAnalyzerAndCodeFix\EmptyLinesAnalyzerAndCodeFix.csproj"</span> <span class="hljs-attr">OutputItemType</span>=<span class="hljs-string">"Analyzer"</span> <span class="hljs-attr">ReferenceOutputAssembly</span>=<span class="hljs-string">"false"</span> <span class="hljs-attr">PrivateAssets</span>=<span class="hljs-string">"All"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Project</span>&gt;</span>
</code></pre>
<p>What we just did was referenced our analyzer from the console project. We then instructed the compiler that the referenced project should be used as a part of the project analysis. Additionally, we told the compiler that our console application will not be referencing the outputs of the analyzer (but we still need it to be built beforehand). If you wish to learn about other specifics of referencing projects, have a look at <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/msbuild/common-msbuild-project-items?view=vs-2022">common MSBuild project items</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#controlling-dependency-assets">how to control dependency assets</a>.</p>
<p>We just achieved the ability to consume the analyzer from the same solution that the analyzer is being developed in (just hit <em>rebuild - and read up on the wonders of</em> <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/framework/mef/"><em>MEF parts</em></a> <em>and</em> <a target="_blank" href="https://github.com/microsoft/vs-mef/blob/main/doc/dynamic_recomposition.md">dynamic recomposition</a> if you ever need to reload the solution and have no idea why).</p>
<p>Let's take the analyzer for a spin!</p>
<h3 id="heading-a-basic-diagnostic-analyzer">A basic diagnostic analyzer</h3>
<p>Head over to our analyzer class library and create a new class named <code>EmptyLinesAnalyzer.cs</code>. There are a couple of namespaces we will be using. Make sure that the following are included.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.Collections.Immutable;
<span class="hljs-keyword">using</span> System.Diagnostics.CodeAnalysis;
<span class="hljs-keyword">using</span> Microsoft.CodeAnalysis;
<span class="hljs-keyword">using</span> Microsoft.CodeAnalysis.Diagnostics;
</code></pre>
<p>Since we are positive we have no idea what VisualBasic even looks like, we will add an attribute specifying that the created class will be a <code>[DiagnosticAnalyzer(LanguageNames.CSharp)]</code>.</p>
<p>Next, we need is to describe what the diagnostic we are producing will report. We need to instantiate a very simple <code>DiagnosticDescriptor</code> (the one from the <code>Microsoft.CodeAnalysis</code> namespace) to report the relevant information to the developer using our analyzer.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// ...</span>
[<span class="hljs-meta">DiagnosticAnalyzer(LanguageNames.CSharp)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EmptyLinesAnalyzer</span> : <span class="hljs-title">DiagnosticAnalyzer</span>
{
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> DiagnosticId = <span class="hljs-string">"DM0001"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> DiagnosticDescriptor Rule = <span class="hljs-keyword">new</span> DiagnosticDescriptor(
        id: DiagnosticId,
        title: <span class="hljs-string">"I work!"</span>,
        messageFormat: <span class="hljs-string">"I Work!"</span>,
        category: <span class="hljs-string">"Design"</span>,
        defaultSeverity: DiagnosticSeverity.Error,
        isEnabledByDefault: <span class="hljs-literal">true</span>,
        description: <span class="hljs-string">"I work!"</span>);

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> ImmutableArray&lt;DiagnosticDescriptor&gt; SupportedDiagnostics
        =&gt; ImmutableArray.Create(Rule);

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Initialize</span>(<span class="hljs-params">AnalysisContext context</span>)</span>
    {
        <span class="hljs-comment">//...</span>
    }
}
</code></pre>
<p>If you recall from the <a target="_blank" href="https://denace.dev/exploring-roslyn-net-compiler-platform-sdk">first post</a> in this series, analyzers can interact with several stages of compilation. Our use case will involve only syntax analysis. The diagnostics, however, will always point to a location in the analyzed syntax.</p>
<p>I would invite you to read up on <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis">syntax analysis</a>, but for now, just imagine our console application has a single syntax tree (the contents of <code>Program.cs</code> ). That tree represents every keyword, variable, expression, comment, and every other character within this file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675376528921/2f951b04-75ce-4806-b635-e4fad86c0d08.gif" alt class="image--center mx-auto" /></p>
<p>Each token in that syntax tree has a span. The character index at which the node begins and its length. The most simple job of an analyzer is to find the location of a code issue (the hard part) and report it to the compiler (the easy part).</p>
<p>Let's do the easy part first.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Initialize</span>(<span class="hljs-params">AnalysisContext context</span>)</span>
{
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
    context.RegisterSyntaxTreeAction(context =&gt;
    {
        <span class="hljs-keyword">var</span> location = Location.Create(context.Tree, TextSpan.FromBounds(<span class="hljs-number">0</span>, context.Tree.Length));
        <span class="hljs-keyword">var</span> diagnostic = Diagnostic.Create(Rule, location);
        context.ReportDiagnostic(diagnostic);
    });
}
</code></pre>
<p>We just instructed the compiler to report an error for any syntax tree encountered and that the <em>error</em> should span the entire document.</p>
<blockquote>
<p>Side note; you would not believe how many autogenerated files exist in a single-line console application. <code>AssemblyInfo</code>, <code>AssemblyAttributes</code>, <code>GlobalUsings</code>, you name it. We can ensure this code is not analyzed by setting the <code>GeneratedCodeAnalysisFlags</code> to <code>None</code>.</p>
</blockquote>
<p>Okay, if all the semicolons are in place, rebuilding the solution will most certainly produce an error.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675377661953/682f58a4-5f50-468f-be95-2288ba59003d.png" alt class="image--center mx-auto" /></p>
<p>Now try to remember the last time you were happy that your code didn't work?!</p>
<p>Anyway, we have a real problem to solve now...</p>
<h3 id="heading-implement-empty-lines-analyzer-logic">Implement <code>empty lines analyzer</code> logic</h3>
<p>Since solving this problem was not intended to be a part of the article (<em>what?!</em>), I will try to leave you with some hints on how to attempt this yourself. If, however, you feel like the extra tinker time is not worth it, I will include a link to a fully working analyzer at the bottom (<em>don't you dare touch that haptic feedback trackpad now!</em>).</p>
<p>To try and solve this problem, we first need to know what parts of the syntax tree the empty lines usually occur. The fastest way to see this is by inspecting the syntax tree of the program code. There is handy tooling already available that allows you to traverse a live syntax tree either in <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/syntax-visualizer?tabs=csharp#syntax-visualizer">Visual Studio</a>, <a target="_blank" href="https://plugins.jetbrains.com/plugin/16902-rossynt">Rider</a>, or even <a target="_blank" href="https://roslynquoter.azurewebsites.net/">online</a>.</p>
<p>Most of the empty lines are a part of <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis#understanding-syntax-trees">syntax trivia</a>. Trivia is a special kind of token found in the syntax tree. It contains syntactically insignificant parts of your code, such as comments, preprocessing directives, whitespaces, and, <em>bingo</em>, new lines.</p>
<p>One way to start is to traverse all leaf tokens in a syntax tree. Once a token with leading trivia is encountered, we need to verify if it is structured so that it contains multiple subsequent empty lines. If found, we need to report the location to the compiler (and we already know how to do that).</p>
<blockquote>
<p>Side note; we should define what multiple empty lines truly mean. Are these just sequential <code>\n</code> characters? How about platform-specific line terminators such as <code>\r\n</code> ? Do we ignore the <code>whitespace</code> and <code>\t</code> characters between new lines?</p>
</blockquote>
<p>One way we could implement the above solution is to analyze each observed syntax tree in the following way.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Recurse</span>(<span class="hljs-params">SyntaxTreeAnalysisContext context, SyntaxNode node</span>)</span>
{
    <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> nodeOrToken <span class="hljs-keyword">in</span> node.ChildNodesAndTokens())
    {
        <span class="hljs-keyword">if</span> (nodeOrToken.IsNode)
            Recurse(context, nodeOrToken.AsNode());
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (nodeOrToken.IsToken)
            AnalyzeToken(context, nodeOrToken.AsToken());
    }
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AnalyzeToken</span>(<span class="hljs-params">SyntaxTreeAnalysisContext context, SyntaxToken token</span>)</span>
{
    <span class="hljs-keyword">if</span> (!token.HasLeadingTrivia)
        <span class="hljs-keyword">return</span>;
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>After finding and reporting all locations at which multiple subsequent empty lines occur, the analyzer will ensure that the compilation process encounters an exception and report the diagnostic we created.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675547139200/c4a400c1-1813-4447-87ba-21b7a898c624.gif" alt class="image--center mx-auto" /></p>
<h3 id="heading-one-more-thing">One more thing</h3>
<p>You might notice that your IDE will begin to issue warnings that <em>a project containing analyzers or source generators should specify the property</em> <code>&lt;EnforceExtendedAnalyzerRules&gt;true&lt;/EnforceExtendedAnalyzerRules&gt;</code>. The compiler platform comes equipped with a <a target="_blank" href="https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/Microsoft.CodeAnalysis.Analyzers.md">plethora of analyzers</a> intended to help with developing and extending the Roslyn compiler.</p>
<p>When the abovementioned property is set in your <code>EmptyLinesAnalyzerAndCodeFix.csproj</code>, these analyzers start analyzing your analyzer and offer valuable guidance on properly handling common scenarios when developing for Roslyn.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1675597191254/e072cf3a-8fd6-4b34-b9b1-615eb9d54a69.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-to-sum-up">To sum up</h3>
<p>If you managed to follow through to the end, you should have a working analyzer that does a couple of really interesting things.</p>
<ul>
<li><p>It is capable of identifying locations that are basically empty space,</p>
</li>
<li><p>it can fail the build when such locations are found within your codebase and offer information on where to find them, and</p>
</li>
<li><p>is fully equipped to annoy your coworkers to no avail.</p>
</li>
</ul>
<p>That may present a challenge but worry not. In <a target="_blank" href="https://denace.dev/fixing-mistakes-with-roslyn-code-fixes">the following article</a>, we will create an accompanying code fix that can fix issues reported by our empty lines analyzer.</p>
<p>And, as promised, check out the <a target="_blank" href="https://github.com/denisekart/exploring-roslyn">denisekart/exploring-roslyn</a> repository, where you can find all the samples from this article (and more).</p>
<p>Until next time, ✌</p>
]]></content:encoded></item><item><title><![CDATA[Exploring Roslyn .NET Compiler Platform SDK]]></title><description><![CDATA[In this series of articles, I will explore how Roslyn enhances the development process and allows developers to write code analysis, transformation, and manipulation tools.
(This series is a work in progress. I will update the article with relevant l...]]></description><link>https://denace.dev/exploring-roslyn-net-compiler-platform-sdk</link><guid isPermaLink="true">https://denace.dev/exploring-roslyn-net-compiler-platform-sdk</guid><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><category><![CDATA[compilers]]></category><category><![CDATA[Metaprogramming ]]></category><dc:creator><![CDATA[Denis Ekart]]></dc:creator><pubDate>Wed, 25 Jan 2023 15:32:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674686378484/4db51c58-5b7f-496c-aca2-970c1c03f562.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this series of articles, I will explore how Roslyn enhances the development process and allows developers to write code analysis, transformation, and manipulation tools.</p>
<p><em>(This series is a work in progress. I will update the article with relevant links as I progress through the series)</em></p>
<ul>
<li><p>Episode 1: Exploring Roslyn .NET Compiler Platform SDK (<em>this article</em>)</p>
</li>
<li><p>Episode 2: <a target="_blank" href="https://denace.dev/getting-started-with-roslyn-analyzers">Getting Started With Roslyn Analyzers</a></p>
</li>
<li><p>Episode 3: <a target="_blank" href="https://denace.dev/fixing-mistakes-with-roslyn-code-fixes"><strong>Fixing Mistakes With Roslyn Code Fixes</strong></a></p>
</li>
<li><p>Episode 4: (soon)</p>
</li>
</ul>
<hr />
<p>What is Roslyn anyway? <em>Okay, let me just..</em>. <code>CTRL+C,</code> <code>CTRL+V</code></p>
<blockquote>
<p>.NET Compiler Platform, also known by its codename Roslyn, is a set of open-source compilers and code analysis APIs for C# and Visual Basic languages from Microsoft.</p>
</blockquote>
<p>Clear, concise. That makes sense, right? But wait, there's more. To better understand Roslyn, we need to go back to its inception (well, we don't, but why not). Here is a brief replay of the events that lead up to all the fun stuff I'm about to describe in this series.</p>
<h3 id="heading-a-very-brief-history-of-roslyn">A (very) brief history of Roslyn</h3>
<ul>
<li><p>On December 2010, Eric Lippert posted an article, <a target="_blank" href="https://learn.microsoft.com/sl-si/archive/blogs/ericlippert/hiring-for-roslyn"><em>Hiring for Roslyn</em></a> announcing a major re-architecture of the C# compiler (and VB compiler too).</p>
</li>
<li><p>At a 2014 Build conference, Microsoft made the Roslyn project open-source under the stewardship of the newly founded .NET Foundation. Roslyn gets shipped with VisualStudio 2015.</p>
</li>
<li><p>In 2015, C# 6 gets released with several <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-60">prominent language features</a>, which somewhat hides the fact, that the C# compiler (and VB compiler too) has been completely rewritten in C# (and VB too), making this year a significant milestone for the language.</p>
</li>
</ul>
<h3 id="heading-compiler-as-a-service">Compiler as a service</h3>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/compiler-api-model">The .NET compiler SDK</a> consists of several layers of APIs, allowing you to effectively integrate into the compilation process, starting at the parsing phase and ending in the emit phase.</p>
<p><img src="https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/media/compiler-api-model/compiler-pipeline-lang-svc.png" alt class="image--center mx-auto" /></p>
<p>The <strong>Compiler API</strong> surface allows you to access the information exposed by the compiler at each stage of the compilation process.</p>
<p>As a part of the compilation process, the compiler may produce diagnostics of varying severity that may be purely informational or expose a compilation error. The <strong>Diagnostic API</strong> allows you to integrate into the pipeline naturally. It enables you to produce a set of custom diagnostic messages or even provide the ability to execute code fixes.</p>
<p><strong>Scripting API</strong> allows you to execute various code snippets effectively, enabling you to use C# as a scripting language. The C# interactive (Read-Evaluate-Print-Loop) is one tool that utilizes this API.</p>
<p>Of course, modern C# tooling allows you to perform various types of code analysis and refactoring jobs. These are all possible because the <strong>Workspaces API</strong> provides a virtual, well, workspace making it possible to format code across projects, find type references and even generate source code.</p>
<p>Okay, now I have a bunch of APIs to which I somehow have access. Any idea what to do with them?</p>
<h3 id="heading-analyze-code-and-report-diagnostics">Analyze code and report diagnostics</h3>
<p>Roslyn allows you to inject components that interact with the compilation process and can ultimately emit diagnostics. These diagnostics can have <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/code-quality/roslyn-analyzers-overview?view=vs-2022#severity-levels-of-analyzers">varying severities</a> and can be used to inform the developer of non-significant compilation events (<em>missing comment on a public member</em>) or can halt the compilation process entirely (<em>missing semicolon at the end of a statement</em>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674593797140/f0f226c8-e13d-43c5-8e07-ee39d6ceba0e.gif" alt class="image--center mx-auto" /></p>
<p>This allows you to tailor the entire compilation process to your project needs and standards. While writing custom code analysis tools may be a good exercise, there is already <a target="_blank" href="https://github.com/topics/roslyn-analyzer">a vast ecosystem of Roslyn analyzers</a> that might fit your needs. These can be installed as <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/code-quality/install-roslyn-analyzers?view=vs-2022">IDE extensions</a> or injected into your project as standard NuGet package dependencies.</p>
<h3 id="heading-provide-code-fixes">Provide code fixes</h3>
<p>As it turns out, nagging about things without having the ability to fix them is pretty useless(<em>a life lesson right here</em>). For that reason, Roslyn allows you to define a code fix corresponding to a diagnostic.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674593843162/8c0ac4ea-fc67-404c-8d2d-acc086ff0da2.gif" alt class="image--center mx-auto" /></p>
<p>Code fixes can be applied manually through <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/ide/quick-actions?view=vs-2022">editor actions</a> or automatically be applied using a tool such as <a target="_blank" href="https://github.com/dotnet/format">dotnet format</a> (so many tools).</p>
<h3 id="heading-rewrite-existing-code">Rewrite existing code</h3>
<p>There are various reasons why you would want to rewrite your code. Perhaps a method has become too complex. Possibly you can improve the code readability by hiding away some implementation details (e.g. <a target="_blank" href="https://en.wikipedia.org/wiki/Declarative_programming">writing declarative code</a>). Whichever reason you may have to refactor your solutions - manually doing so can be tedious work.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674595408597/a6e53cea-dcdd-4d78-ba0f-44a510e1b435.gif" alt class="image--center mx-auto" /></p>
<p>Roslyn allows you to plug in a refactoring solution that can analyze your syntax to provide intelligent means of automatically refactoring code.</p>
<h3 id="heading-generate-source-code">Generate source code</h3>
<p>Have you heard of the saying <em>the best code is no code at all</em>? It turns out source code is evil. It has bugs. It rots over time. It requires maintenance. It needs engineers to write it.</p>
<p>Wait, what am I saying? I'm an engineer. I love writing code, though not all code. There is code I am always hesitant to start writing. Not because it's hard but because it's solving the same problem I have solved numerous times before.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1674596146334/2160b9ca-3f9b-4d8e-8040-32a793b6a4bd.png" alt class="image--center mx-auto" /></p>
<p>Roslyn allows you to automate this too. You can <s>use</s> create a source generator that can automatically analyze relevant parts of your solution to spit out code without you typing a single syntax token (well, apart from writing the actual source generator).</p>
<h3 id="heading-before-we-proceed">Before we proceed</h3>
<p>Just a fair warning. The tools described in this article integrate perfectly with <a target="_blank" href="https://visualstudio.microsoft.com/">Microsoft VisualStudio IDE</a> and will work on any updated version of VisualStudio 2022 (and above). That being said, the Roslyn Compiler Platform is a part of the mentioned IDE, meaning that the utilities described in this series of articles will (with minor exceptions) work in any IDE that supports modern .NET and will even work without an IDE(<a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet">dotnet CLI</a>).</p>
<p>All examples in these articles were developed using .NET 7, VisualStudio 2022 Community Edition, and JetBrains Rider.</p>
<p><a target="_blank" href="https://denace.dev/getting-started-with-roslyn-analyzers">Let's write some code!</a></p>
]]></content:encoded></item></channel></rss>