MagnifyingGlass

Using Analyzers in Your Projects

While many of my posts cover the basics of creating analyzers and digging into the syntax, this post is going to focus on how to add existing analyzers to your projects. There has already been a great deal of work done by many people to create a wide array of analyzers and you should take advantage of those on top of creating your own analyzers.

The easiest way to add analyzers to your project is to obtain them via Nuget. A few popular analyzers include the Microsoft.AnalyzerPowerPack and StyleCop.Analyzers. Simply open up the Nuget package manager and search for the analyzer that you want to install.

NugetPackageManager

Once installed, you will see the default settings that the analyzer developer decided upon for severity of the violation. Your code may end up looking something like this:

AnalyzerIssues

Luckily, you have full control over each and every analyzer that is provided to you and you can decide what should be reported and the severity of the violation. Under your project references, you will see a new node named Analyzers.

ProjectHierarchy

You can right click on this node and select the Open Active Rule Set option. This will bring up a dialog that lets you configure the rules.

RulesetWindow

You’ll notice an information bar at the top indicating that you are viewing the default ruleset. If you make any modifications, then it those changes will be persisted to a new file named <projectname>.ruleset. From this window, you can change the behavior of rules. You have the following options for each rule:

Value Definition
Warning Will appear as a warning in the Visual Studio Error List window.
Error Will appear as an error in the Visual Studio Error List window.
Info Will appear as an information message in the Visual Studio Error List window.
Hidden Analysis is still performed, but it is not reported by any Visual Studio UI. A custom UI could be created to report these diagnostics.
None The rule is disabled and the diagnostic will not be run against your code.
Inherit This will inherit the behavior from the rule group. There is currently no UI to set this.

I recommend getting as many analyzers as you can get your hands on to see what value they add. After you see what analyzers are available, you can trim back the analyzers you use to match your business needs. If you have a favorite analyzer that you think other developers needs to know about, leave a comment below.

Image Credit: Kit using Creative Commons License

Yolo

Code Analyzers: They aren’t just for C#

Visual Basic has always had a little place in my heart. VB5 was the language I used at my first job and VB.Net was the language I used to transition into managed code. Luckily for us, Microsoft has kept improving VB.Net and the Roslyn compiler supports it as a first class citizen, just like C#.

So I am going to show you how to write an analyzer that analyzes Visual Basic code, but I will write the analyzer in C#, as that is my preferred language. To start analyzing Visual Basic code, you need to add a the Microsoft.CodeAnalysis.VisualBasic nuget package to your project.

Now, when creating a new analyzer, you need to make sure you indicate it is a Visual Basic analyzer by setting the DiagnosticAnalyzer attribute to include LanguageNames.VisualBasic. You should also add usings for the Microsoft.CodeAnalysis.VisualBasic and Microsoft.CodeAnalysis.VisualBasic.Syntax to ensure that the type you need to analyze for Visual Basic code are available.

using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;

namespace AnalyzerSamples.VB
{
    [DiagnosticAnalyzer(LanguageNames.VisualBasic)]
    public class OptionExplicitAndOptionStrictShouldNotBeTurnedOff : DiagnosticAnalyzer
    {
      //...
    }
}

In this analyzer, we are going to analyze Option Statements and ensure that Option Explicit and Option Strict are not set to Off. To do this, we will register a syntax node action for SyntaxKind.OptionStatement. Note that this SyntaxKind is in the Microsoft.CodeAnalysis.VisualBasic.Syntax, instead of the CSharp namespaces we have used in past analyzers.

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.OptionStatement);
}

Our AnalyzeSyntax method will run a few checks to make sure that we are working with a valid option statement (remember analyzers can run against code that is not compilable) and that it is a type that we care about. To start, our method simply gets the option statement and makes sure it has both the Name and Value keywords. If not, we stop analyzing.

private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
    // TODO: Replace the following code with your own analysis, generating Diagnostic objects for any issues you find
    var optionStatement = context.Node as OptionStatementSyntax;

    if (optionStatement == null)
        return;

    if (optionStatement.NameKeyword.IsMissing || optionStatement.ValueKeyword.IsMissing)
        return;

Next we are going to look at the name keyword and check it’s kind. Since we are checking Explicit and Strict, we will look to see if it is a SyntaxKind.Explicit or SyntaxKind.Strict:

// Look to see if the keyword is Strict or Explicit.
if (!(optionStatement.NameKeyword.IsKind(SyntaxKind.StrictKeyword) || optionStatement.NameKeyword.IsKind(SyntaxKind.ExplicitKeyword)))
    return;

After we have validated that it is an option we care about, we can then check the value to see if it is on or off. This is as easy as checking the ValueKeyword to see if its SyntaxKind is SyntaxKind.OffKeyword:

// We only care if it is set to Off
if (!optionStatement.ValueKeyword.IsKind(SyntaxKind.OffKeyword))
    return;

If we have then reached this point in the code, we know that one of the two keywords is set to off and we can raise a diagnostic at the correct location with the correct message. Notice that we use the ValueText of the NameKeyword to allow the message to correctly show the name of the keyword that is causing the violation.

// For all such symbols, produce a diagnostic.
var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), optionStatement.NameKeyword.ValueText);
context.ReportDiagnostic(diagnostic);

The full code for the analyzer is:

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.VisualBasic;
using Microsoft.CodeAnalysis.VisualBasic.Syntax;

namespace AnalyzerSamples.VB
{
    [DiagnosticAnalyzer(LanguageNames.VisualBasic)]
    public class OptionExplicitAndOptionStrictShouldNotBeTurnedOff : DiagnosticAnalyzer
    {
        public const string DiagnosticId = "OptionExplicitAndOptionStrictShouldNotBeTurnedOff";
        internal static readonly LocalizableString Title = "Option strict and option explicit should not be turned off";
        internal static readonly LocalizableString MessageFormat = "Option {0} should not be turned off.";
        internal const string Category = "Visual Basic";

        internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, true);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

        public override void Initialize(AnalysisContext context)
        {
            context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.OptionStatement);
        }

        private static void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
        {
            var optionStatement = context.Node as OptionStatementSyntax;

            if (optionStatement == null)
                return;

            if (optionStatement.NameKeyword.IsMissing || optionStatement.ValueKeyword.IsMissing)
                return;

            // Look to see if the keyword is Strict or Explicit.
            if (!(optionStatement.NameKeyword.IsKind(SyntaxKind.StrictKeyword) || optionStatement.NameKeyword.IsKind(SyntaxKind.ExplicitKeyword)))
                return;

            // We only care if it is set to Off
            if (!optionStatement.ValueKeyword.IsKind(SyntaxKind.OffKeyword))
                return;

            // For all such symbols, produce a diagnostic.
            var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), optionStatement.NameKeyword.ValueText);

            context.ReportDiagnostic(diagnostic);

        }
    }
}

That’s it. Writing analyzers for Visual Basic specific code is just as easy as writing analyzers for C# code. Many analyzers may be able to share a good majority, if not all, of their code to analyze both C# and Visual Basic. So, next time you are writing an analyzer, see how hard it would be to make it work for both C# and Visual Basic. A VB programmer may thank you for it.

LikeAGlove

Working With Catch Blocks in your Roslyn Analyzer

Exceptions. Most developers have a love/hate relationship with them. And catching general exceptions is a topic that gets discussed over and over and over. Regardless of which side of the fence you are on, you may run into a scenario when you want to write an analyzer that can enforce your team’s standards when working with exceptions. Roslyn offers a few different SyntaxKinds which you can register for that give you access to various parts of a catch statement.

Syntax Kind Description
SyntaxKind.CatchClause This represents the entirety of the catch statement and the body of the work to be done inside the catch clause.
SyntaxKind.CatchDeclaration This represents the variable declaration portion of the catch clause. So in the catch clause catch (Exception ex) when (i==1), this node would represent Exception ex
SyntaxKind.CatchFilterClause This represents the filter provided as part of the catch clause. So in the catch clause catch (Exception ex) when (i==1), this node would represent i==1
SyntaxKind.CatchKeyword This represents the catch keyword that is part of a catch clause.

So, now that we know the different types of syntax nodes we can work with, we can put them to use in a custom analyzer. For example, say you wanted to raise a diagnostic anytime a System.Exception was caught. As we covered in the Working with Types post, you need to capture the type you want to check from the Compilation engine. Since in this case we care about System.Exception, we will capture that from a CompilationStartAction.

context.RegisterCompilationStartAction((compileContext) =>
{
    var exceptionType = compileContext.Compilation.GetTypeByMetadataName("System.Exception");

Next we need to register our code to run on a SyntaxKind.CatchDeclaration.

compileContext.RegisterSyntaxNodeAction((symbolContext) =>
{
}, SyntaxKind.CatchDeclaration);

Inside the body of this action, we can check a few things. First, we should check that the declaration has a type. The main reasons it may not have a type is that the user is currently typing and they haven’t gotten there yet, or just that the document is in a non-compilable state. If we run into this scenario, we will just stop analyzing for now.

var errorSymbol = symbolContext.Node as CatchDeclarationSyntax;

if (errorSymbol.Type.IsMissing)
{
    return;
}

After that, we simply need to get the type of the variable from the semantic model and check it against the exceptionType we stashed away earlier.

var variableTypeInfo = symbolContext.SemanticModel.GetTypeInfo(errorSymbol.Type).ConvertedType as INamedTypeSymbol;

if (variableTypeInfo == null)
    return;

if (variableTypeInfo.Equals(exceptionType))
{
    symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, errorSymbol.GetLocation()));
}

So, the full diagnostic is now:

context.RegisterCompilationStartAction((compileContext) =>
{
    var exceptionType = compileContext.Compilation.GetTypeByMetadataName("System.Exception");
    compileContext.RegisterSyntaxNodeAction((symbolContext) =>
    {
        var errorSymbol = symbolContext.Node as CatchDeclarationSyntax;

        if (errorSymbol.Type.IsMissing)
        {
            return;
        }

        var variableTypeInfo = symbolContext.SemanticModel.GetTypeInfo(errorSymbol.Type).ConvertedType as INamedTypeSymbol;

        if (variableTypeInfo == null)
            return;

        if (variableTypeInfo.Equals(exceptionType))
        {
            symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, errorSymbol.GetLocation()));
        }
    }, SyntaxKind.CatchDeclaration);
});

At this point, we have a fully functioning analyzer that raises a diagnostic anytime a general exception is caught. While this is nice, there is also the scenario where there is no CatchDeclaration specified, i.e. the user just has a catch keyword on a line. For these scenarios, we can register a SyntaxNodeAction for a SyntaxKind.CatchClause and check to see if a declaration exists. If no declaration exists, then we can raise the diagnostic.

compileContext.RegisterSyntaxNodeAction((symbolContext) =>
{
    var errorSymbol = symbolContext.Node as CatchClauseSyntax;

    if (errorSymbol.Declaration == null)
    {
        symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, errorSymbol.CatchKeyword.GetLocation()));
        return;
    }
}, SyntaxKind.CatchClause);

As you can see, the code analysis tools in Roslyn make it easy to write an analyzer that will help nudge your team along to use exceptions correctly (for various definitions of correct :) ).

Image Credit: Andrei Niemimäki via Creative Commons

ShippingContainers

Creating an analyzer based on Generic Type Parameters

In my last post we discussed how to work with types in your analyzers. We even covered the basics of getting a generic type. In this post, we will go a little deeper and look at how you can analyze the generic type parameters used in the creation of a generic type. We will even delve a little into construction parameters, so we can write an analyzer that verifies you specify an IEqualityComparer when you use a string as the key to your dictionary.

To start, as a refresher from last time, in order to access types from the Roslyn compiler we need to register a compilation start action and get the metadata type by name.

context.RegisterCompilationStartAction(compilationContext =>
{
    var dictionaryTokenType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");

To get the dictionary type, we are using the name of the generic class plus the arity to determine the metadata name.

Once we have done that, we can then register a SyntaxNodeAction for all ObjectCreationExpressions. We will start by verifying the type is a dictionary type.

compilationContext.RegisterSyntaxNodeAction(symbolContext =>
{
    var creationNode = (ObjectCreationExpressionSyntax)symbolContext.Node;
    var variableTypeInfo = symbolContext.SemanticModel.GetTypeInfo(symbolContext.Node).ConvertedType as INamedTypeSymbol;

    if (variableTypeInfo == null)
        return;

    if (!variableTypeInfo.OriginalDefinition.Equals(dictionaryTokenType))
        return;
...

Now we are at a point where we can start looking at the generic type parameters to determine if this is a dictionary we care about. Since I am looking for a string as the key, I can use the SpecialType.System_String to check against a type. To access the generic type parameters, you examine the TypeArguments on the INamedTypeSymbol.

if (variableTypeInfo.TypeArguments.First().SpecialType != SpecialType.System_String)
    return;

Since we know we are dealing with a dictionary and it uses the key as the first parameter, we simply check the first type argument to see if it is a string. Now that we have the type determination out of the way we can now start looking at the constructor arguments to determine if an equality comparer was passed in. First, in our compilation start action we need to capture the IEqualityComparer type:

var equalityComparerInterfaceType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.IEqualityComparer`1");

Again, this is a generic with an arity of 1, so we can use that information to generate the correct metadata name. Next we can access the ArgumentList off of the ObjectCreatoinExpressionSyntax node to check to see if any of the arguments match that type. The first check to perform is to simply check if there are any constructor parameters sent:

var arguments = creationNode.ArgumentList?.Arguments;
if (arguments==null || arguments.Value.Count == 0)
{
    symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, symbolContext.Node.GetLocation()));
    return;
}

Next, we can loop over the arguments to see if any of them are of the type IEqualityComparer`1. If there is no equality comparer, then we can raise a diagnostic:

bool hasEqualityComparer = false;
foreach (var argument in arguments)
{
    var argumentType = symbolContext.SemanticModel.GetTypeInfo(argument.Expression);

    if (argumentType.ConvertedType == null)
        return;    

    if (argumentType.ConvertedType.OriginalDefinition.Equals(equalityComparerInterfaceType))
    {
        hasEqualityComparer = true;
        break;
    }
}

if (!hasEqualityComparer)
{
    symbolContext.ReportDiagnostic(Diagnostic.Create(Rule, symbolContext.Node.GetLocation()));
}

Now that we have all of that wired up, our analyzer will properly report a diagnostic on a dictionary with a string key that does not specify the equality comparer when the dictionary is constructed.

Given the following sample code, you can see when the diagnostic would be triggered and when it would not:

// Will trigger diagnostic
var foo = new Dictionary<string, string>();
var foo1 = new Dictionary<string, string>(6);
var foo2 = new Dictionary<string, string>(foo);

// Will not trigger diagnostic
var bar1 = new Dictionary<string, string>(StringComparer.CurrentCultureIgnoreCase);
var bar2 = new Dictionary<string, string>(6, StringComparer.CurrentCultureIgnoreCase);
var bar3 = new Dictionary<string, string>(bar2, StringComparer.CurrentCultureIgnoreCase);

The full text of the analyzer is available on github if you want to test it out for yourself.

If you have ever used a string as a key to a dictionary and have been bitten by case sensitivity issues, this type of rule could have helped you prevent those types of bugs before they ever happened. Clearly, there is no magic going on here and it is very easy to wire up an analyzer that can make your life easier in just a few short minutes. At some point in the future, we can revisit this analyzer and see how we could handle the ToDictionary extension method to ensure that any dictionary with a string key is done correctly in our applications.

Image Credit Roy Luck via Creative Commons

typeface

Working with Types in Your Analyzer

If you want to write an analyzer that checks for certain conditions for a given type, you are going to have to familiarize yourself in working with types in Roslyn. Types returned to you from various Roslyn methods will return to you an object that implements ITypeSymbol. This interface has various methods that can provide you with the information you need to make decisions in your analyzer. You can get types for various nodes in the semantic model by using the SemanticModel.GetTypeInfo and then using either the Type or ConvertedType off of the TypeInfo that is returned. The difference between the Type and ConvertedType is that the ConvertedType is the type after any implicit conversions have been applied. If there are no implicit conversions, then Type and ConvertedType are identical.

There are some types that are special and can be checked by using the SpecialKind enum. For example, if you have an ITypeSymbol and you want to check if it is a string, you could simply do the following.

if (variableTypeInfo.SpecialType == SpecialType.System_String)
{
}

One question that you may have is how to get an ITypeSymbol if it is not defined in the SpecialKind enum. I had the same question and posted it to StackOverflow. I got an answer from Kevin Pilch-Bisson (@Pilchie) which states:

The normal pattern for doing this is to use Compilation.GetTypeByMetadataName(), and then compare that ITypeSymbol with The one you got back from SemanticModel.GetTypeInfo().

Note: Make sure to use .Equals to compare ITypeSymbol instances, as some of them do not guarantee reference identity.

So, if we register a CompilationStartAction, then we can get at a type by simply using the GetTypeByMetadataName method:

var arrayListType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.ArrayList");

So, now that I have the ArrayList ITypeSymbol, I can now use that to compare to other ITypeSymbol interfaces I get back from the semantic model.

For example, I could have an analyzer that prohibits the usage of ArrayList. I can register a SyntaxNodeAction for all ObjectCreationExpressoin syntax kinds and use the GetTypeInfo method to get the type of the creation and determine if it is an array list.

public override void Initialize(AnalysisContext context)
{
    context.RegisterCompilationStartAction(AnalyzeArrayList);
}

private static void AnalyzeArrayList(CompilationStartAnalysisContext compilationContext)
{
    var arrayListType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.ArrayList");

    compilationContext.RegisterSyntaxNodeAction(syntaxContext =>
    {
        var variableTypeInfo = syntaxContext.SemanticModel.GetTypeInfo(syntaxContext.Node).Type as INamedTypeSymbol;

        if (variableTypeInfo == null)
            return;

        if (variableTypeInfo.Equals(arrayListType))
        {
            syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, syntaxContext.Node.GetLocation()));
        }
    }, SyntaxKind.ObjectCreationExpression);
}

As the name implies, the GetTypeByMetadataName uses the metadata name of the type to find the type. One place where this might catch you is when dealing with generics. For example, when working with a dictionary, you might be tempted to try one of the following:

var dictionaryType = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary <string, string>");
var dictionaryType2 = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary <TKey, TValue>");

But the Metadata name for the generic dictionary is Dictionary`2. For generics it is always the name of the type followed by the arity (number of type parameters) that make up the metadata name (e.g. List`1, Tuple`5). For more info, you can read up on type names and arity encoding.

So the correct way to get a generic dictionary is

var dictionaryType2 = compilationContext.Compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");

If you are still unsure of the correct value to use for the GetTypeByMetadataName method, you can look at the MetadataName property that is available on all objects that implement ISymbol.

As you can see working with types in code analyzers is not too difficult once you know where to look and how to obtain the types you care about.

Image Source evan p. cordes via Creative Commons

1 2 3 7