Seal

Creating an Analyzer For Sealing Classes

I was talking with a co-worker today when the topic of code analyzers came up. He stated that one analyzer he would really like to see is one that warns you if you are not sealing classes that do not explicitly define abstract or virtual methods. This is somewhat of a religious debate among developers. If you don’t believe me, just read the comments on Eric Lippert’s post on the subject. I am not going to debate the validity of the claims on either side, rather I am going to focus on how one would implement an analyzer to see if a class should be sealed if it does not explicitly define functionality to be extended.

To start, I will register an analyzer to look at all class declarations.

context.RegisterSyntaxNodeAction((syntaxNodeContext) =>
{
} , SyntaxKind.ClassDeclaration);

Next, I will check if the class is static using the Modifiers property. If it is, I won’t bother analyzing it:

var node = syntaxNodeContext.Node as ClassDeclarationSyntax;

// We don't care about sealing static classes
if (node.Modifiers.Where(x => x.IsKind(SyntaxKind.StaticKeyword)).Any())
    return;

We can also rule out any classes that are already sealed by checking the Modifiers for the SealedKeyword:

// The class is already sealed, no reason to analyze it
if (node.Modifiers.Where(x => x.IsKind(SyntaxKind.SealedKeyword)).Any())
    return;

At this point, we have a non-static, non-sealed class, so we need to check the points that can be extended via inheritance. Based on the MSDN docs for abstract and virtual, we know that we need to check methods, properties, events, and indexers. We can get those from the Members of the class:

var methods = node.Members.Where(x => x.IsKind(SyntaxKind.MethodDeclaration));
var props = node.Members.Where(x => x.IsKind(SyntaxKind.PropertyDeclaration));
var events = node.Members.Where(x => x.IsKind(SyntaxKind.EventDeclaration));
var indexers = node.Members.Where(x => x.IsKind(SyntaxKind.IndexerDeclaration));

Next, we just have to loop over those declarations to determine if any of them are abstract or virtual. If any of them are, then we know we don’t need to raise a diagnostic:

foreach (var m in methods)
{
    var modifiers = (m as MethodDeclarationSyntax)?.Modifiers.Where(x=>x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
    if (modifiers != null && modifiers.Any())
        return;
}

foreach (var p in props)
{
    var modifiers = (p as PropertyDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
    if (modifiers != null && modifiers.Any())
        return;
}

foreach (var e in events)
{
    var modifiers = (e as EventDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
    if (modifiers != null && modifiers.Any())
        return;
}

foreach (var i in indexers)
{
    var modifiers = (i as IndexerDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
    if (modifiers != null && modifiers.Any())
        return;
}

Finally, if we have gone through all of the possible inheritance points and still have not hit an explicit declaration of intent for inheritance, we can raise our diagnostic:

// We got here, so there are no abstract or virtual methods/properties/events/indexers
syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation()));

So, our full diagnostic is:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction((syntaxNodeContext) =>
    {
        var node = syntaxNodeContext.Node as ClassDeclarationSyntax;

        // We don't care about sealing static classes
        if (node.Modifiers.Where(x => x.IsKind(SyntaxKind.StaticKeyword)).Any())
            return;

        // The class is already sealed, no reason to analyze it
        if (node.Modifiers.Where(x => x.IsKind(SyntaxKind.SealedKeyword)).Any())
            return;

        var methods = node.Members.Where(x => x.IsKind(SyntaxKind.MethodDeclaration));
        var props = node.Members.Where(x => x.IsKind(SyntaxKind.PropertyDeclaration));
        var events = node.Members.Where(x => x.IsKind(SyntaxKind.EventDeclaration));
        var indexers = node.Members.Where(x => x.IsKind(SyntaxKind.IndexerDeclaration));

        foreach (var m in methods)
        {
            var modifiers = (m as MethodDeclarationSyntax)?.Modifiers.Where(x=>x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
            if (modifiers != null && modifiers.Any())
                return;
        }

        foreach (var p in props)
        {
            var modifiers = (p as PropertyDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
            if (modifiers != null && modifiers.Any())
                return;
        }

        foreach (var e in events)
        {
            var modifiers = (e as EventDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
            if (modifiers != null && modifiers.Any())
                return;
        }

        foreach (var i in indexers)
        {
            var modifiers = (i as IndexerDeclarationSyntax)?.Modifiers.Where(x => x.IsKind(SyntaxKind.AbstractKeyword) || x.IsKind(SyntaxKind.VirtualKeyword));
            if (modifiers != null && modifiers.Any())
                return;
        }

        // We got here, so there are no abstract or virtual methods/properties/events/indexers
        syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation()));

    } , SyntaxKind.ClassDeclaration);
}

I have also posted the full diagnostic in my analyzer samples project on GitHub.

Despite what side of the argument you are for in this debate, you can see how to create an analyzer that would allow catch classes that are not intended for inheritance and ensure they are not inheritable. It is up to you whether you would include this analyzer in your list of enabled analyzers.

Image Credit: Northwest Power and Conservation Council via Creative Commons

facebooktwittergoogle_plusreddit
Alone

Creating a Stand-Alone Code Analyzer

Many of my past posts have covered the process of creating an analyzer that is part of a Visual Studio addin or nuget package. However, there are times that you might want to just create a command line utility to do some analysis of a project. This type of project is very useful in prototyping out ideas or writing a one off utility to get an idea of the state of a solution. It can also be very useful for consultants wanting to run a custom set of tools against customer solutions.

To create a stand alone code analyzer, you can select the Stand-Alone Code Analysis Tool project template from the Extensibility sections of the new project window.

NewStandAloneProject

At this point, you just have a basic console application that is referencing the code analysis DLLs. To start analyzing a project, you need to load it up. To do this, we will create a MSBuildWorkSpace and load the solution file:

var ws = Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Create();
var soln = ws.OpenSolutionAsync(@"Z:\Dev\Temp\SimpleWinformsTestApp\SimpleWinformsTestApp.sln").Result;

Next we will get the first project from the solution and get the Compilation object, so that we can access things like the SyntaxTree.

var proj = soln.Projects.Single();
var compilation = proj.GetCompilationAsync().Result;

Now that we have the Compliation object we have a myriad of options available to us and we can start doing analysis. In this example, I want to find all classes that inherit from System.Windows.Forms.Form. So, as I described in the Working With Types in Your Analyzer post, we can get the type by its metadata name form the Compilation object:

string TEST_ATTRIBUTE_METADATA_NAME = "System.Windows.Forms.Form";
var testAttributeType = compilation.GetTypeByMetadataName(TEST_ATTRIBUTE_METADATA_NAME);

To get at the classes declared in the project, we need to loop over all of the SyntaxTrees in the Compilation and then find all of the ClassDeclarationSyntax nodes declared in those trees:

foreach (var tree in compilation.SyntaxTrees)
{
    var classes = tree.GetRoot().DescendantNodesAndSelf().Where(x => x.IsKind(SyntaxKind.ClassDeclaration));
    foreach (var c in classes)
    {
     // ...
    }
// ...
}

Once we have all of the classes, we need to determine if the class inherits from the testAttributeType we declared earlier in the analysis. The ClassDeclarationSyntax has a BaseList property which defines all of the base classes for the class. So from these base classes, we can get the type information (from the SemanticModel) and compare it to the testAttributeType to see if it is a Windows Form:

var classDec = (ClassDeclarationSyntax)c;
var bases = classDec.BaseList;

if (bases?.Types != null)
{
    foreach (var b in bases.Types)
    {
        var nodeType = compilation.GetSemanticModel(tree).GetTypeInfo(b.Type);
        // Is the node a System.Windows.Forms.Form?
        if (nodeType.Type.Equals(testAttributeType))
        {
            Console.WriteLine(classDec.Identifier.Text);
        }
    }
}

The full program is as follows:

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace StandAloneCodeAnalysis
{
    class Program
    {
        static void Main(string[] args)
        {
            var ws = Microsoft.CodeAnalysis.MSBuild.MSBuildWorkspace.Create();
            var soln = ws.OpenSolutionAsync(@"Z:\Dev\Temp\SimpleWinformsTestApp\SimpleWinformsTestApp.sln").Result;
            var proj = soln.Projects.Single();
            var compilation = proj.GetCompilationAsync().Result;

            string TEST_ATTRIBUTE_METADATA_NAME = "System.Windows.Forms.Form";
            var testAttributeType = compilation.GetTypeByMetadataName(TEST_ATTRIBUTE_METADATA_NAME);

            foreach (var tree in compilation.SyntaxTrees)
            {
                var classes = tree.GetRoot().DescendantNodesAndSelf().Where(x => x.IsKind(SyntaxKind.ClassDeclaration));
                foreach (var c in classes)
                {
                    var classDec = (ClassDeclarationSyntax)c;
                    var bases = classDec.BaseList;

                    if (bases?.Types != null)
                    {
                        foreach (var b in bases.Types)
                        {
                            var nodeType = compilation.GetSemanticModel(tree).GetTypeInfo(b.Type);

                            // Is the node a System.Windows.Forms.Form?
                            if (nodeType.Type.Equals(testAttributeType))
                            {
                                Console.WriteLine(classDec.Identifier.Text);
                            }
                        }
                    }
                }
            }
            Console.ReadKey();
        }
    }
}

As you can see, creating an analyzer that works as a standalone project is not too different from creating an analyzer that runs inside of Visual Studio. There are some basic differences and you are required to setup a little more yourself, but the underlying logic does not change much.

Image Credit: Vincent Lock via Creative Commons

facebooktwittergoogle_plusreddit
Order

Analyzing the Order of Method Calls

Recently there was a question on StackOverflow that piqued my interest. The question was basically asking how an analyzer could ensure that one method on an instance was called prior to calling another method on an instance.

An example of this would be if you wanted to ensure you called the HasValue method on a System.Nullable before you called the Value property. I provided an answer to the question, but let’s dig a little deeper here.

Since we want to look at System.Nullable, we’ll first need to get that type to check it against variable types when doing our analysis. To do this, we will register an action to run on compilation start, as I demonstrated in the Working with Types in Your Analyzer post. We’ll also register a syntax node action to analyze all MethodDeclaration nodes.

context.RegisterCompilationStartAction((compilationStartContext) =>
{
    var nullableType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Nullable`1");
    compilationStartContext.RegisterSyntaxNodeAction((analysisContext) =>
    {
        // ...
    }, SyntaxKind.MethodDeclaration);
});

Next, we will capture all MemberAccessExpressionSyntax nodes from the method, which will give us all nodes where an object is accessed (i.e. method calls, property calls, field accesses, …).

var invocations =
    analysisContext.Node.DescendantNodes().OfType<MemberAccessExpressionSyntax>();

We’ll also create a HashSet<string> to keep track of all the HasValue calls for a given variable.

var hasValueCalls = new HashSet<string>();

Now we can iterate over the invocations to keep track of the HasValue and Value calls. To start, we check if the Expression on the invocation is anIdentifierNameSyntax`:

foreach (var invocation in invocations)
{
    var e = invocation.Expression as IdentifierNameSyntax;

    if (e == null)
        continue;

    //...
}

Next we will use the Semantic Model to get the type information from the expression. To do this we simply, call the GetTypeInfo method on the SemanticModel:

var typeInfo = analysisContext.SemanticModel.GetTypeInfo(e).Type as INamedTypeSymbol;

We now have the type info, so we will check that it is a System.Nullable type. Since all base nullable types (i.e. int?, bool?, …) are constructed from the System.Nullable type, we can use the nullableType we captured earlier to verify the type.

if (typeInfo?.ConstructedFrom == null)
    continue;

if (!typeInfo.ConstructedFrom.Equals(nullableType))
    continue;

At this point, we know that the variable is a System.Nullable, so we can now check if the invocation is a HasValue or a Value call. If it is a HasValue call, we will add that variable to our HashSet, so we know that HasValue has been called on this variable.

string variableName = e.Identifier.Text;

if (invocation.Name.ToString() == "HasValue")
{
    hasValueCalls.Add(variableName);
}

Finally, we can check the Value calls and see if there was a previous HasValue call for the variable name. If not, we can raise a diagnostic.

if (invocation.Name.ToString() == "Value")
{
    if (!hasValueCalls.Contains(variableName))
    {
        analysisContext.ReportDiagnostic(Diagnostic.Create(Rule, e.GetLocation()));
    }
}

So now, if we try some code against this analyzer, we will see the following code raise a diagnostic:

int? x = null;
var n = x.Value;  

While the following code will not raise a diagnostic:

int? x = null;
if (x.HasValue)
{
    var n = x.Value;
}

In the end, the full diagnostic is:

public override void Initialize(AnalysisContext context)
{
    context.RegisterCompilationStartAction((compilationStartContext) =>
    {
        var nullableType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Nullable`1");
        compilationStartContext.RegisterSyntaxNodeAction((analysisContext) =>
        {
            var invocations =
                analysisContext.Node.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
            var hasValueCalls = new HashSet<string>();
            foreach (var invocation in invocations)
            {
                var e = invocation.Expression as IdentifierNameSyntax;

                if (e == null)
                    continue;

                var typeInfo = analysisContext.SemanticModel.GetTypeInfo(e).Type as INamedTypeSymbol;

                if (typeInfo?.ConstructedFrom == null)
                    continue;

                if (!typeInfo.ConstructedFrom.Equals(nullableType))
                    continue;

                string variableName = e.Identifier.Text;

                if (invocation.Name.ToString() == "HasValue")
                {
                    hasValueCalls.Add(variableName);
                }

                if (invocation.Name.ToString() == "Value")
                {
                    if (!hasValueCalls.Contains(variableName))
                    {
                        analysisContext.ReportDiagnostic(Diagnostic.Create(Rule, e.GetLocation()));
                    }
                }
            }
        }, SyntaxKind.MethodDeclaration);
    });
}

Getting the order of method calls can be a bit tricky, but once you work out the logic and the syntax nodes that you need to process, the work to be done becomes clear. If you have a story of an analyzer you wrote that does something similar, share it in the comments below.

Image Credit: Sebastien Wiertz via Creative Commons

facebooktwittergoogle_plusreddit
branches

Working with If Blocks in Your Code Analyzers

Once you start writing your analyzers, you will eventually run into a scenario where you have to handle an if block. If blocks like any other blocks of code have a few nuances that you need to understand when working with them.

To start analyzing if blocks, you can register a SyntaxNodeAction for the SyntaxKind.IfStatement:

context.RegisterSyntaxNodeAction((syntaxNodeContext)=>
{
}, SyntaxKind.IfStatement); 

Now before we start analyzing the IfStatementSyntax, we should first look at the components of the syntax. Take for example the following code:

IfBlock

Breaking down this statement, each of the different colors maps to different properties on the IfStatementSyntax as demonstrated in the chart below.

Statement Explanation
if This is represented by the IfKeyword property on the IfStatementSyntax
(x==null) This is represented by the Condition property on the IfStatementSyntax
{ // Do stuff } This is represented by the Statement property on the IfStatementSyntax
else { // Do other stuff } This is represented by the Else property on the IfStatementSyntax

Now that you have a basic understanding of the different elements in the IfStatementSyntax, you can take the node passed into the method via the syntaxNodeContext to access the If statement. So, if for example we wanted to raise a diagnostic anytime someone checked != null, we could start by looking at the condition as a BinaryExpressionSyntax

var node = syntaxNodeContext.Node as IfStatementSyntax;
var binaryExpression = node.Condition as BinaryExpressionSyntax;

if (binaryExpression == null)
    return;

Next, the binaryExpression has a kind that we can check to make sure it is a NotEqualsException.

if (binaryExpression.IsKind(SyntaxKind.NotEqualsExpression))
{
}

The binary expression contains a left and right portion of the expression. Since we are looking to see if one side of the expression is null, we can simply cast the left and right portions of the expression to a LiteralExpressionSyntax and check to see if that is a NullLiteralExpression. If either side matches that criteria, then we can raise the diagnostic.

var left = binaryExpression.Left as LiteralExpressionSyntax;
var right = binaryExpression.Right as LiteralExpressionSyntax;

if (left != null && left.IsKind(SyntaxKind.NullLiteralExpression) 
    ||(right !=null && right.IsKind(SyntaxKind.NullLiteralExpression)))
{
    syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(Rule, binaryExpression.GetLocation()));
}

So in the end, our full diagnostic looks like this:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction((syntaxNodeContext)=>
    {
        var node = syntaxNodeContext.Node as IfStatementSyntax;
        var binaryExpression = node.Condition as BinaryExpressionSyntax;

        if (binaryExpression == null)
            return;

        if (binaryExpression.IsKind(SyntaxKind.NotEqualsExpression))
        {
            var left = binaryExpression.Left as LiteralExpressionSyntax;
            var right = binaryExpression.Right as LiteralExpressionSyntax;

            if (left != null && left.IsKind(SyntaxKind.NullLiteralExpression) 
                ||(right !=null && right.IsKind(SyntaxKind.NullLiteralExpression)))
            {
                syntaxNodeContext.ReportDiagnostic(Diagnostic.Create(Rule, binaryExpression.GetLocation()));
            }
        }
    }, SyntaxKind.IfStatement);
}

As I demonstrated, the processing of if statements in your analyzers is not difficult and can be a very powerful construct to use in your analyzer. There is obviously much more that can be done with the processing of if statements and I would encourage you to take the time and learn more about them.

Image Credit Benson Kua via Creative Commons

facebooktwittergoogle_plusreddit
Struture

Analyzing XML Comments in your Roslyn Code Analyzer

In previous articles, we covered the basics of dealing with comments in code analyzers. You’ll recall that comments and other white space are referred to as trivia by the Roslyn compiler. XML Comments are also considered trivia, however, they are a special case of trivia called structured trivia.

If, for example, you wanted to check to see that all public methods have some sort of XML comment, you could simply write an analyzer like this:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(CheckMethods, SyntaxKind.MethodDeclaration);
}

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;
    if (node.HasStructuredTrivia)
        return;

    if (node.Modifiers.Any(SyntaxKind.PublicKeyword))
        syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(Rule,node.GetLocation()));
}

All that we are checking in that code is that the method has no structured trivia and that it is a public method. At this point, the analyzer really isn’t doing anything useful, other than ensuring that the method has a leading xml comment indicator (///). Next we could modify this to verify that it actually has a summary node in the XML. To do this, we need to get the structured XML and then find the XmlElementSytnax nodes that have a name of summary.

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;

    if (!node.Modifiers.Any(SyntaxKind.PublicKeyword))
        return;

    var xmlTrivia = node.GetLeadingTrivia()
        .Select(i => i.GetStructure())
        .OfType<DocumentationCommentTriviaSyntax>()
        .FirstOrDefault();

    var hasSummary = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Any(i => i.StartTag.Name.ToString().Equals("summary"));

    if (!hasSummary)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
           Diagnostic.Create(Rule, node.Identifier.GetLocation(), "Missing Summary"));
    }
}

If we find the summary is missing, we raise a diagnostic at the Identifier location, such that only the method name gets underlined.

We could take this a bit further by checking the parameters. To start we need to get the XmlElementSyntax nodes whose name are param. From those nodes, we will get all attributes that are of the type XmlNameAttributeSyntax.

var allParamNameAttributes = xmlTrivia.ChildNodes()
    .OfType<XmlElementSyntax>()
    .Where(i => i.StartTag.Name.ToString().Equals("param"))
    .SelectMany(i => i.StartTag.Attributes.OfType<XmlNameAttributeSyntax>());

Once this query is evaluated, allParamNameAttributes contains a list of all name attributes within the XML comments. Using these nodes, we can compare the identifiers to the names of the parameters in the method declaration and raise any diagnostics where a parameter is not documented.

foreach (var param in node.ParameterList.Parameters)
{
    var existsInXmlTrivia = allParamNameAttributes
                .Any(i=>i.Identifier.ToString().Equals(param.Identifier.Text)) ;// ()

    if (!existsInXmlTrivia)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
             Diagnostic.Create(Rule, param.GetLocation(), "Parameter Not Documented"));
    }
}

Putting it all together yields:

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;

    if (!node.Modifiers.Any(SyntaxKind.PublicKeyword))
        return;

    var xmlTrivia = node.GetLeadingTrivia()
        .Select(i => i.GetStructure())
        .OfType<DocumentationCommentTriviaSyntax>()
        .FirstOrDefault();

    var hasSummary = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Any(i => i.StartTag.Name.ToString().Equals("summary"));

    if (!hasSummary)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
            Diagnostic.Create(Rule, node.Identifier.GetLocation(), "Missing Summary"));
    }

    var allParamNameAttributes = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Where(i => i.StartTag.Name.ToString().Equals("param"))
        .SelectMany(i => i.StartTag.Attributes.OfType<XmlNameAttributeSyntax>());

    foreach (var param in node.ParameterList.Parameters)
    {
        var existsInXmlTrivia = allParamNameAttributes
                    .Any(i=>i.Identifier.ToString().Equals(param.Identifier.Text)) ;// ()

        if (!existsInXmlTrivia)
        {
            syntaxNodeAnalysisContext.ReportDiagnostic(
               Diagnostic.Create(Rule, param.GetLocation(), "Parameter Not Documented"));
        }
    }
}

As you can see, it is possible to get at the XML comments for any given node in the syntax tree, provided you know where to look. There is obviously a lot more work that could be put into this analyzer to ensure you are following all the rules. If you are looking for a complete analyzer, look no further than the StyleCopAnalyzers project on GitHub. They have a handful of documentation rules that cover the cases I outlined and many more. Note that some of the code in this article to get at the XML trivia was based on code that exists in the XmlCommentHelpers in the StyleCopyAnalyzers project.

Image Credit: Stéfan via Creative Commons

facebooktwittergoogle_plusreddit
1 2 3 9