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
Params

Working with Modifiers in Your Code Fix

Recently there was a question on StackOverflow asking how to remove params from a method parameter list. There is a lot of extra information in that question, so I thought I would distill it down and explain some things in more detail.

To start, we need an analyzer that raises a diagnostic on the parmas keyword. We’ll start by just raising it on every paramas keyword, as we really care more about the code fix in this case.

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction((syntaxContext) =>
    {
        var node = syntaxContext.Node as ParameterListSyntax;
        if (node.Parameters.LastOrDefault().Modifiers.Any(x => x.IsKind(SyntaxKind.ParamsKeyword)))
        {
            syntaxContext.ReportDiagnostic(Diagnostic.Create(Rule, node.GetLocation()));
        }
    }, SyntaxKind.ParameterList);
}

In this case, we are registering for all ParameterLists and checking if that last keyword has a ParamsKeyword modifier. The params keyword is considered a modifier, similar to public, private, static, etc …

Now that we have an analyzer that can detect params, we want to remove them. To start, we will simply register our code fix:

var diagnostic = context.Diagnostics.First();
context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token => {

Next, we are going to get the document and syntax root and use the root to find the parameters list that raised the diagnostic.

var document = context.Document;
var root = await document.GetSyntaxRootAsync(token);

var allParmeters = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterListSyntax;

Since we are looking for params and they have to be the last parameter, we are just looking at the last parameter in the parameter list.

var lastParameter = allParmeters.Parameters.LastOrDefault();

Now that we have the correct parameter, we are going to get all modifiers off the paramter except the params keyword and place them in a new SyntaxTokenList of modifiers.

// Keep all modifiers except the params
var newModifiers = lastParameter.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
syntaxModifiers.AddRange(newModifiers);

Since all SyntaxNodes are immutable, we need to get a new SytnaxNode with the correct modifiers. The Roslyn API provides a WithModifiers extension method that does just that for us.

var updatedParameterNode = lastParameter.WithModifiers(syntaxModifiers);

Finally we will replace the old node with the new node in the Syntax tree and return that.

var newDoc = document.WithSyntaxRoot(root.ReplaceNode(lastParameter, updatedParameterNode));
return newDoc;

In the end, the full code fix looks like this:

public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{

    var diagnostic = context.Diagnostics.First();
    context.RegisterCodeFix(CodeAction.Create("Remove 'params' modifier", async token =>
    {

        var document = context.Document;
        var root = await document.GetSyntaxRootAsync(token);

        var allParmeters = root.FindNode(diagnostic.Location.SourceSpan, false) as ParameterListSyntax;

        var lastParameter = allParmeters.Parameters.LastOrDefault();

        // Keep all modifiers except the params
        var newModifiers = lastParameter.Modifiers.Where(m => !m.IsKind(SyntaxKind.ParamsKeyword));
        var syntaxModifiers = SyntaxTokenList.Create(new SyntaxToken());
        syntaxModifiers.AddRange(newModifiers);

        var updatedParameterNode = lastParameter.WithModifiers(syntaxModifiers);

        var newDoc = document.WithSyntaxRoot(root.ReplaceNode(lastParameter, updatedParameterNode));
        return newDoc;
    }, "KEY"), diagnostic);
}

And there you have it. A simple code fix that removes the params modifier from a parameter list. As you can see, this code is pretty straightforward, and the concepts can be easily extended to remove any modifiers that are present on a SyntaxNode.

Image Credit: Kurt Bauschardt via Creative Commons

facebooktwittergoogle_plusreddit
1 2 3 9