Using Roslyn to look for code smells

Posted on Friday, 09 Oct 2015

Since first hearing about Roslyn I was instantly excited about its application in being able to easily parse C# and could clearly see the benefits it could bring anyone wishing to perform analysis on an existing codebase - not to mention the great things it can do for the Visual Studio plugins and extensions.

Having been playing around with Roslyn for a couple of days now I thought I'd share a cool snippet of code I put together to demonstrate how easy it is to parse C# code using the power of just a few Roslyn NuGet packages.

In this using Roslyn to analyse an existing codebase and flag any classes that are guilty of the 'Too many parameters' code smell.

First, let's load the solution:

First of all we're going to need to load our solution...

public static class WorkspaceSolution
{
    public static IEnumerable<Document> LoadSolution(string solutionDir)
    {
        var solutionFilePath = Path.GetFullPath(solutionDir);

        MSBuildWorkspace workspace = MSBuildWorkspace.Create();
        Solution solution = workspace.OpenSolutionAsync(solutionFilePath).Result;

        var documents = new List<Document>();
        foreach (var projectId in solution.ProjectIds)
        {
            var project = solution.GetProject(projectId);
            foreach (var documentId in project.DocumentIds)
            {
                Document document = solution.GetDocument(documentId);
                if (document.SupportsSyntaxTree) documents.Add(document);
            }
        }

        return documents;
    }
}

As you can see from the above snippet this is actually quite simple thanks to the Microsoft.CodeAnalysis.MSBuild package, more specifically the MSBuildWorkspace class. This allows us to scan our solution and create a collection of any documents within it.

Analysing the documents (aka. class files)

Now we've got a list of all of the documents within our solution we can begin to iterate through them, parsing the syntax tree until we come to the method parameters (though performing the same analysis on constructors is equally as easy - but for the sake of this demonstration we'll look at methods).

At this point we can check to see if the number of parameters exceeds our threshold.

List<MethodDeclarationSyntax> methods = documents.SelectMany(x => x.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MethodDeclarationSyntax>()).ToList();

var smellyClasses = new Dictionary<string, int>();
int paramThreshold = 5;
foreach (MethodDeclarationSyntax methodDeclarationSyntax in methods)
{
    ParameterListSyntax parameterList = methodDeclarationSyntax.ParameterList;
    if (parameterList.Parameters.Count >= 5)
    {
        smellyClasses.Add(methodDeclarationSyntax.Identifier.SyntaxTree.FilePath, parameterList.Parameters.Count);
    }
}

Let's put it to use

If you're still not sold on how powerful this can be, let's move this into a unit test that can easily be included as a base suite of unit tests - allowing us to easily enforce rules on our development teams.

[TestCase(5)]
public void Methods_ShouldNotHaveTooManyParams(int totalParams)
{
    List<MethodDeclarationSyntax> methods = this.documents.SelectMany(x => x.GetSyntaxRootAsync().Result.DescendantNodes().OfType<MethodDeclarationSyntax>()).ToList();
    foreach (MethodDeclarationSyntax methodDeclarationSyntax in methods)
    {
        ParameterListSyntax parameterList = methodDeclarationSyntax.ParameterList;

        // Assert
        parameterList.Parameters.Count.ShouldBeLessThanOrEqualTo(totalParams, "File Location: " + methodDeclarationSyntax.Identifier.SyntaxTree.FilePath);
    }
}

As you'll see in the code snippet below we then use Shouldly unit testing framework and its awesome error messages to inform us of the class that's currently violating our code smell rule!

If you're interested in having a play with this then you can find the code up on GitHub here. I'd also recommend checking out this post if you're interested in learning more about Roslyn and how it works. Another great resource that gave me the idea for using Roslyn to create tests is this strathweb.com blog post by @filip_woj.

Back