I recently started reading up on the subject of the .NET Compiler Platform and SDK (aka Roslyn) and all the things you could do with it in terms of code quality, analyzers and code fixers.
Update (02-Nov-2020):
This is now a part of an actual series of posts which can be found here:
First of all there there is a lot of materials on Roslyn out there from the official Microsoft Docs to collections of blogs, Github repositories and open source projects, so this article wont go into details beyond trying to give some high level context for the rest of the post.
So what is the .NET Compiler Platform known as Roslyn all about?!
- Roslyn is a set of APIs and Services usable through an individual SDK, that allow us to access the .NET Compiler pipeline and have access to a lot of information about lots of aspects of our projects and code.
- It also includes APIs that enable us to make changes to the code or projects via our own implemented refactoring actions.
- The .NET Compiler pipeline breaks down the code compilation in several steps. Roslyn allows us to hook in each of those and access different types of information.
- We have access to low level
Syntax Trees
that describe how the code is structuredSyntax Trees
are data structures that describe the syntax or structure of the code.- The
Syntax Tree
is expressed through types classes and collections of different types of nodes. - The example we will look at will use the
Syntax Tree
, the collections of different types ofSyntax Nodes
,Syntax Tokens
andSyntax Trivia
to determine if the problem issue is present. More - More Information Here!
- We can build or modify our own
Syntax Trees
which means building and modifying code using theSyntaxFactory
and other Roslyn API constructs. - We also have access to APIs and services that can tell us how the code is
interconnected, and what it actually and how it is interpreted beyond just the syntax through the
Semantic
analysis APIs. - On top of all this there are also APIs that allow us to perform major changes that range from code refactoring to project and solution level refactoring and changes.
Another aspect of the .Net Compiler SDK are the project templates which we can use to implement our own tools that utilize the SDK. The output of these projects are Visual Studio Extensions that can be installed and used during development.
The primary goal of this set of articles is to go over the implementation of a basic code analysis tool that comes with a simple fix for the issue it’s reporting.
What we are essentially therefore building is aVisual Studio Extension
others can install, that notifies us if our code has specific issue and offers automatic refactoring to get rid of the problem.
For a lot more information presented in a much more better way check out the documents here.
Term and Concept Introductions
Two key terms that will be used through the article are Diagnostic/s
and Fix Provider
or Fix
.
Diagnostics
Diagnostics are what we use to refer to the issues or problems our analysis tool or Analyzer is going to show/report if the extension was installed in an instance of Visual Studio and if the problem we are analyzing the code for is present.
Visual studio and tools like ReSharper already come with a large set of Analyzers that report different types of diagnostics.
An example of a reported Diagnostic in the VS IDE is:
The Variable 'VAR_NAME' is assigned but its value is never used
Diagnostics are reported and visible on the UI with squiggly lines under the relevant code and actions (and quick actions) on the left hand side to apply the fixes.
Fix or Fix Providers
Fix Providers or Fixes are changes to code that can be applied to get rid of reported Diagnostics. An example of a Fix for the above Diagnostic would be:
Remove unused variable
Fixes are visible on the UI and can be “applied” via quick actions to any diagnostic.
The sample problem
The problem that our basic extension will address is a potential code smell of having multiple method calls using the same parameters in the same block of code.
For the purposes of the example we make some initial assumptions about the methods and the code. The example analyzer and fixer therefore are not complete and can further be extended to cover a large set of edge cases we will touch upon in the later articles.
The simple example that illustrates the issue:
// ...
public static void Main()
{
var input = "Bar";
if(Foo(input) != null)
{
Console.WriteLine(Foo(input));
}
}
public static string Foo(string input){
if(input.Length > 2){
return "FooValue:"+input;
}
else{
return null;
}
}
// ...
The data the method returns for the same input
is used in several places in the Main
code block. If the method did something that was quite expensive for the program it would be better to have a single call and store the result instead of making multiple calls.
Additionally Foo(string input)
is a pure method. Every time it is called with the same parameter the value returned is the same.
Because of all this the two calls in Main()
are therefore not needed. Even though Foo
is very simple we can “save” a call even in this simple program by storing the return value from an initial call in a variable. The “fixed” output therefore would be:
// ...
public static void Main()
{
var input = "Bar";
var fooResult = Foo(input);
if(fooResult != null)
{
Console.WriteLine(fooResult);
}
}
public static string Foo(string input){
if(input.Length > 2){
return "FooValue:"+input;
}
else{
return null;
}
}
// ...
This is exactly what our sample analyzer and code fixer will do!
The Project Template
We will now look at the Project Template we use and go over the solution it creates. As mentioned installing the SDK for Roslyn allows us to use the templates it provides to create the Extension that will contain the our analysis and fix provider code.
We will use the Analyzer with Code Fix (.Net Standard)
which out of the box creates a solution with 3 Projects.
If we create a new sample BlogAnalyzer
solution based on the template we get the following projects created:
- BlogAnalyzer
- The Project containing the Analyzer and Fix Provider that already register with the Roslyn APIs and contain initialization code that can be configured to address our specific problems.
- BlogAnalyzer.Vsix
- Vsix are projects that build and generate Visual Studio Extension Installers. This one references the
BlogAnalyzer
and packages the Analyzer and Fix provider in an Visual Studio extension that can be installed in Visual Studio
- Vsix are projects that build and generate Visual Studio Extension Installers. This one references the
- BlogAnalyzer.Test
- Test project containing initial tests as well as a set of test helpers specifically built out to follow a TDD approach for our extension.
The example problem the default template project is addressing is a fictional code smell of lowercase Type/Class names.
Developing from the template using TDD and the Vsix project
Because of the nature of the solution and the output being a Visual Studio Extension the test project is going to be key in quickly developing the analyzer and fix provider.
The test project comes with a set of helpers, asserters and other classes. These are all nicely integrated with existing Roslyn classes like Document
and others used for verifying our extension code.
An overview of the test project setup and the key pieces:
- Verifiers
DiagnosticVerifier
- Used to verify/assert the diagnostics reported from the our Analyzer.
CodeFixVerifier
which inherits from theDiagnosticVerifier
- Used to verify both the reported diagnostics and code fixes after they are applied as provided by the Fix Provider.
- Helper Classes
DiagnosticResult
- A class we can use to create expected objects on the
Diagnostics
reported by our Analyzer
- A class we can use to create expected objects on the
DiagnosticVerifier.Helper
- Additional helpers and extensions for the Diagnostic Verifier
CodeFixVerifier.Helper
- Additional helpers and extensions for the Diagnostic Verifier
The main test file for our solution is BlogAnalyzerUnitTests.cs
which inherits from the CodeFixVerifier
. It contains two test methods as a starting point:
TestMethod1()
- Asserts that our extension does not report issues when it should not. It asserts this with a simple empty string.
TestMethod2()
- Asserts that the extension both reports the diagnostics and that the code fix is valid. The test case is a simple class declaration.
Remember the template code issue being analyzed is a fictional one where we assume a convention of Class/Type names being all uppercase.
The text case used and expected result used inTestMethod2()
:
var test = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication1
{
class TypeName
{
}
}";
var fixtest = @"
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace ConsoleApplication1
{
class TYPENAME
{
}
}";
Moving forward we can refactor these methods to better suit our needs.
We can also turn the test methods to DataTestMethods
where we can supply both the code input and expected output via test data parameters.
VSIX Project
We can also always use the .vsix
project to test out our extension. Vsix
projects can run in Debug
mode. This starts a new debug instance of Visual Studio with our extension installed.
We can use this instance to create a new project (any kind), start writing some code and introduce the issues we are looking for intentionally. We should then see our extension report the diagnostic and provide us with the quick fix actions to fix it.
This is useful to test out the UI aspects of our extension as we can define some text/string values that explain to the developers using our extension what problems were found.
For example we can define a UI string for our action: Clear multiple method invocation
- Which is something we can visually test when we are actually using the extension.
Important to note that VSIX is a much broader project type. It can be used to implement may types of extensions for Visual Studio that go beyond just code issue analysis and fixes. Example: VSIX can also install/extend the default project templates.
Additional Tools
To help us out with the analysis, visualizing the issue try to discover as well as the fixes and new code we need to construct we can use some very useful tools.
Visual Studio Syntax Visualizer
The Visual Studio Syntax Visualizer Window
is available after installing the .NET Compiler SDK. It’s one of the more useful tools to understand how the Syntax Trees
are constructed, the types of nodes/tokens/trivia compose them and how they can be traversed and searched. This is all critical for the Analyzer part of our extension.
If we use the Syntax Visualizer
for Code Example 2
we would get the following view:
We see that our code is represented by a tree like structure of typed objects with the CompilationUnit
as the root. CompilationUnit
translates to a source code file.
Besides the tree structure, the visualizer offers a Properties panel where we can see additional useful details for each of the selected nodes in the tree.
We notice some interesting nodes like InvocationExpression
, LocalDeclarationStatement
and Block
that we might potentially use to analyze and search for our problem code. We will look at all of these in more detail in the next article that covers the analyzer part of our extensions.
Roslyn quoter
Another useful is the Roslyn Quoter!
It’s a web tool where you can paste any piece of valid C# code and it would generate the Syntax Factory
Code to generate the snippet/method/expression/source file.
For example we can take the Foo()
method from our Code Examples and paste it in the editor window, select Parse as Member
and Get Roslyn API calls to generate this code file!
This would give us the Roslyn Calls starting with MethodDeclaration
to build out the Foo()
method! This is using the SyntaxFactory but by default assumes SyntaxFactory has been referenced as a using static
.
We can select the Do not require 'using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;'
option if we want to also see the actual usage of SyntaxFactory
public static void Main()
{
SyntaxFactory.MethodDeclaration(
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
SyntaxFactory.Identifier("Foo"));
// .... Rest Of The Code here
}
Summary
The goal of this initial post was to try and introduce the .NET Compiler Platform SDK (Roslyn) and some of the possibilities it offers for solving code smells by looking at the given example issue.
This post also covered some of the basic tools we can use to achieve the goal:
- The
SyntaxTree
is thetool
we use to identify the issue. - The
SyntaxFactory
is thetool
we use to fix the issue and generate new code. - The
Syntax Visualizer
is going to help us identify the syntax nodes involved in our code smell. - The knowledge of
Roslyn APIs
that integrate with the Compiler Pipeline allowing us to hook in the developer experience and run code that reports diagnostics and provides fixes. - Bringing all of this together is the job of the
Analyzer with Code Fix (.Net Standard)
template.- The projects it generates are the umbrella that brings everything together as a VS Extension project.
The next parts of this article series will look at how
we use the above tools to both analyze, report and fix our example code smell!
If this all sounds interesting and you want to find out more please have a look at this collection of appropriately named “awesome” list or Roslyn resources: