Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Writing and Distributing Roslyn Analyzers with MyGet

DZone's Guide to

Writing and Distributing Roslyn Analyzers with MyGet

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

Pretty sweet: MyGet just announced Vsix support has been enabled for all MyGet customers! I wanted to work on a fun example for this new feature and came up with this: how can we use MyGet to build and distribute a Roslyn analyzer and code fix? Let’s see.

Developing a Roslyn analyzer and code fix

Roslyn analyzers and code fixes allow development teams and individuals to enforce certain rules within a code base. Using code fixes, it’s also possible to provide automated “fixes” for issues found in code. When writing code that utilizes DateTime, it’s often best to use DateTime.UtcNow instead of DateTime.Now. The first uses UTC timezone, while the latter uses the local time zone of the computer the code runs on, often introducing nasty time-related bugs. Let’s write an analyzer that detects usage of DateTime.Now!

You will need Visual Studio 2015 RC and the Visual Studio 2015 RC SDK installed. You’ll also need the SDK Templates VSIX package to get the Visual Studio project templates. Once you have those, we can create a new Analyzer with Code Fix.

image_thumb[2]

A solution with 3 projects will be created: the analyzer and code fix, unit tests and a Vsix project. Let’s start with the first: detecting DateTime.Now in code an showing a diagnostic for it. It’s actually quite easy to do: we tell Roslyn we want to analyze IdentifierName nodes and it will pass them to our code. We can then see if the identifier is “Now” and the parent node is “DateTime”. If that’s the case, return a diagnostic:

public override void Initialize(AnalysisContext context)
    {
        context.RegisterSyntaxNodeAction(AnalyzeIdentifierName, SyntaxKind.IdentifierName);
    }

    private void AnalyzeIdentifierName(SyntaxNodeAnalysisContext context)
    {
        var identifierName = context.Node as IdentifierNameSyntax;
        if (identifierName != null)
        {
            // Find usages of "DateTime.Now"
            if (identifierName.Identifier.ValueText == "Now"
                && ((IdentifierNameSyntax)((MemberAccessExpressionSyntax)identifierName.Parent).Expression).Identifier.ValueText == "DateTime")
            {
                // Produce a diagnostic.
                var diagnostic = Diagnostic.Create(Rule, identifierName.Identifier.GetLocation(), identifierName);

                context.ReportDiagnostic(diagnostic);
            }
        }
    }

If we compile our solution and add the generated NuGet package to another project, DateTime.Now code will be flagged. But let’s implement the code fix first as well. We want to provide a code fix for the syntax node we just detected. And when we invoke it, we want to replace the “Now” node with “UtcNow”. A bit of Roslyn syntax tree fiddling:

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

        var diagnostic = context.Diagnostics.First();
        var diagnosticSpan = diagnostic.Location.SourceSpan;

        // Find "Now"
        var identifierNode = root.FindNode(diagnosticSpan);

        // Register a code action that will invoke the fix.
        context.RegisterCodeFix(
            CodeAction.Create("Replace with DateTime.UtcNow", c => ReplaceWithDateTimeUtcNow(context.Document, identifierNode, c)),
            diagnostic);
    }

    private async Task<Document> ReplaceWithDateTimeUtcNow(Document document, SyntaxNode identifierNode, CancellationToken cancellationToken)
    {
        var root = await document.GetSyntaxRootAsync(cancellationToken);
        var newRoot = root.ReplaceNode(identifierNode, SyntaxFactory.IdentifierName("UtcNow"));
        return document.WithSyntaxRoot(newRoot);
    }

That’s it. We now have an analyzer and a code fix. If we try it (again, by adding the generated NuGet package to another project), we can see both in action:

image_thumb[6]

Now let’s distribute it to our team!

Distributing a Roslyn analyzer and code fix using MyGet

Roslyn analyzers can be distributed in two formats: as NuGet packages, so they can be enabled for individual project, and as a Visual Studio extension so that all projects we work with have the analyzer and code fix enabled. You can build on a developer machine, a CI server or using MyGet Build Services. Let’s pick the latter as it’s the easiest way to achieve our goal: compile and distribute.

Create a new feed on www.myget.org. Next, from the Build Services tab, we can add a GitHub repository as the source. We’ve open-sourced our example at https://github.com/myget/sample-roslyn-with-vsix so feel free to add it to your feed as a test. Once added, you can start a build. Just like that. MyGet will figure out it’s a Roslyn analyzer and build both the NuGet package as well as the Visual Studio extension.

image_thumb[9]

Sweet! You can now add the Roslyn analyzer and code fix per-project, by installing the NuGet package from the feed (https://www.myget.org/F/datetime-analyzer/api/v2). ANd when registering it in Visual Studio (https://www.myget.org/F/datetime-analyzer/vsix/) by opening the Tools | Options... menu and the Environment | Extensions and Updates pane, you can also install the full extension.

image_thumb[12]

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
c-sharp ,roslyn ,c#

Published at DZone with permission of Maarten Balliauw, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}