Roslyn analyzers allow companies and individuals to enforce certain rules within a code base. My understanding is that there are two primary uses for analyzers:
- Broadly enforce coding styles and best practices
- Specifically guide individuals consuming a library
The first use is largely a replacement for tools like StyleCop and FxCop. We can use analyzers to enforce stylistic choices like “All private variables must start with a lowercase letter” and “Use spaces not tabs”. In fact, you can start using StyleCop.Analyzers today. From a NuGet command line simply use:
Install-Package StyleCop.Analyzers -Pre
The second use is to release library specific analyzers meant to guide consumers of your library. For example, we might want to ensure that no one does the following:
System.DateTime is immutable, so the above code is misleading. Instead the user should have written the following:
Analyzers allow library authors to help guide their users. In that sense, I hope that it becomes standard to release a set of analyzers alongside new libraries. It’s difficult to say if this will actually happen, as it requires extra work from library authors.
Download the Roslyn SDK Templates
The templates do not ship with Visual Studio 2015. To install them go to:
Tools > Extensions and Updates > Online.
Search for “Roslyn SDK” and find the templates that correspond to your version. I’m using Visual Studio 2015 RC. I’ve chosen the package selected below:
After installing the templates, you must restart Visual Studio.
Creating your first analyzer
File > New Project > Extensibility > Analyzer with Code Fix
Give your analyzer a name and click “OK”. I’ve taken the creative liberty of naming mine
"Analyzer1". From here we’re presented a README that explains that building our project creates both a
.vsix for Visual Studio and a
.nupkg for submission to NuGet. There are also instructions on how to properly distribute your analyzer as a NuGet package.
Let’s take a look at what we’re given right out of the box:
We’re given three projects:
Analyzer1– The brain of our analyzer. This is where all code analysis is done and code fixes are figured out.
Anylzer1.Test– A default test project with some helper classes to make testing easier.
Analyzer.Vsix– The startup project that will be deployed to Visual Studio. The
.vsixmanifesttells Visual Studio that you’d like to export an analyzer and a code fix.
To run the project, simply press F5. A new instance of Visual Studio will launch. This Visual Studio is called the Experimental Hive and has its own set of settings within the Windows Registry. Note: It’s a good practice to choose a different theme for your Experimental Hive so you don’t get them mixed up.
Once you open a solution, you’ll notice Visual Studio complaining about a lot of new warnings. The analyzer we’re running simply creates a warning when it sees any type with lowercase letters in its name. It’s obviously not very useful, but allows us to also demonstrate the code fix included in this sample:
Now that we’ve got a rough idea of what each project is for, we’ll explore
Analyzer1 and what we’re given for free.
The first thing to notice is that our Analyzer inherits from the abstract class
DiagnosticAnalyzer. This class expects us to do two things:
- Expose a set of diagnostics our analyzer is responsible for via
- Initialize our analyzer via
Let’s take a look at the properties and fields in the first half of the file:
It may seem overwhelming at first, but bear with me. First notice the DiagnosticAnalyzer attribute applied to the class. This specifies what language or languages our analyzer will be run on. Today, you can only specify C# and VB .Net.
Looking within the class, the first five properties are simply strings to describe our analyzer and provide messages to users list. By default, the analyzer is set up to encourage localization and allows you define your
message format and
description as localizable strings. However if localization scares you like it does me, you make them simple strings.
Take a moment to look at
DiagnosticDescriptor Rule. It defines a DiagnosticSeverity of “Warning”. I suspect you’ll likely want to stick with Warning, but if you feel like imposing on consumers of your analyzer, you could upgrade the severity to Error and prevent compilation completely. Note: I don’t recommend this. If your analyzer misbehaves and reports errors where there are none, the user will remove it.
Finally, lets take a look at the two generated methods:
Initialize() method sets up the analyzer by registering the
AnalyzeSymbol method to fire when semantic analysis has been run on a
NamedType symbol. This is only one example out of a handful of ways to trigger an analyzer. We can register our analyzer to run on various triggers including compilation, analysis of codeblocks and analysis of syntax trees. We’ll flush out
AnalysisContext in further posts.
AnalyzeSymbol() method is where we actually do the analysis we’ve been talking about. This is where we would use the Syntax Tree and Symbol APIs to diagnose and report issues. In the case of this analyzer it simply takes the
INamedTypSymbol provided and checks whether any of the characters in its name are lowercase. If they are, we report this diagnostic using the
Rule we defined earlier.
This may seem like an awful lot of boilerplate for such a simple analyzer. However, once you start building complicated analyzers, you’ll find that the analysis code quickly starts to dominate and that the boilerplate isn’t so bad.
Next time, we’ll explore the
CodeFixProvider and how we can offer solutions to problems we find in a user’s code.