Hi, Habr!
Not so long ago I went to the
CLRium conference from
sidristij , where I saw a rather simple and convenient way to analyze the C # source code in MSVS 2015.
The task is taken from the project in which I participate: each argument with a reference type must have the NotNull or CanBeNull attributes (which are then
used by ReSharper ). In reality, of course, in the projects themselves, the attributes are only part of the checks, but this does not prevent them from being mandatory. There are already tests that check the build and fall if the methods or constructors do not contain the required attributes, but developers still rather often forget to put them down, which leads to build failures, code updates, etc. Now, if Visual Studio together with ReSharper would give out warnings that the code is not quite good, then it would be possible to save time and nerves ...
')
And, in fact, with the new studio it becomes possible! Moreover, it is impossible to make your own checks.
Source code can be found here .
So, first you need to have
Microsoft Visual Studio 2015 downloaded. I advise you to carefully choose the language, since I did not look and downloaded the Russian version, with a very non-standard translation.
Next, you need to download the extension for MSVS ".NET Compiler Platform SDK Templates" (
see details here , thanks
Ordos )
Next, create a project for our code analyzer:

As a result, the studio will create three projects: a working project, tests and a special project for the vsix extension. The first project is needed to implement our analyzer + to create Code Fixes, that is, prompts in the studio with a suggestion to correct. There are two ways to distribute a package: through the vsix extension and through nuget. The first allows you to install checks for Visual Studio on the current computer without affecting projects. The second method allows you to check the code at design time (and, on any computer, the nuget package is downloaded), as well as at build time (even if Visual Studio is not installed), it works in previous releases of Visual Studio. To create a nuget package, it is enough just to have a nuspec of the file and a pair of scripts, however, an additional project is created for the vsix extension (which is shown here for example only).
The project with tests is also interesting: we can test and debug our analyzer without launching Visual Studio separately! We simply create a C # file, load it into the compiler, and it returns the Warning / Error list!
Initially, Visual Studio creates a template parser, which requires that all types have a naming in UPPERCASE. And the tests are sharpened on him.
To change the behavior, let's do the following changes with the class inherited by DiagnosticAnalyzer:
1. Change the DiagnosticId to your own. This is the key of our warning'a (with type String). The programmer will see it in the list of errors, CodeFix will react to it, the SuppressMessage attribute will use it. To accidentally with no one intersect, it is best to choose a longer name. I chose it as <package nuget name> _ <internal id>: NullCheckAnalyzer_MethodContainsNulls
2. Then it is best to change all the descriptions to your own: see methods Title, MessageFormat, Description, Category.
3. Further, in the Initialize method, we change the arguments of the RegisterSymbolAction function: we will respond not to types, but to methods. By the way, judging by my research and
Roslyn source codes , some of the SymbolKind values are not supported at all (that is, for example, we cannot respond to the parameters).
3. Change the AnalyzeSymbol method a bit:
- At the entrance we will receive a token to check
- For each error, you must add to the context information about it. That is, for one method you can find as many errors as you like, and with different Id.
It turns out the following code:
[DiagnosticAnalyzer(LanguageNames.CSharp)] public class NullCheckAnalyzer : DiagnosticAnalyzer { public const string ParameterIsNullId = "NullCheckAnalyzer_MethodContainsNulls"; // You can change these strings in the Resources.resx file. If you do not want your analyzer to be localize-able, you can use regular strings for Title and MessageFormat. internal static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); internal static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); internal const string Category = "Naming"; internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(ParameterIsNullId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); private static readonly ImmutableArray<DiagnosticDescriptor> supportedDiagnostics = ImmutableArray.Create(Rule); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => supportedDiagnostics; public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method); } private static void AnalyzeSymbol(SymbolAnalysisContext context) { var methodSymbol = context.Symbol as IMethodSymbol; if (ReferenceEquals(null, methodSymbol) || methodSymbol.DeclaredAccessibility == Accessibility.Private) { return; } foreach (var parameter in ParametersGetter.GetParametersToFix(methodSymbol)) { var type = methodSymbol.ContainingType; // For all such symbols, produce a diagnostic. var diagnostic = Diagnostic.Create(Rule, parameter.Locations[0], methodSymbol.Name, type.Name, parameter.Name); context.ReportDiagnostic(diagnostic); } } }
Everything, now our small analyzer already will force the Visual Studio to pour errors. To test run the tests. Microsoft carefully created two: checking that the empty file is correct, and checking that the diagnostics + fixes work correctly. The first one is completed correctly, and the second one with an error, since we have not done Code Fix.
I tried to quickly make Code Fix and realized that even such a simple fix already contains nuances that are not so easy to solve:
- From what namespace do I add the NotNull attribute? From Resharper. *? And if there are several options: your attributes and the package from Resharper?
- How to add using: inside the namespace, or on top of the file? Will there be collisions? Perhaps it is better to register alias?
- If there is no reference to an assembly with attributes, then it should be added, however, according to which rules? Take the first available, or try to download from the site nuget? Or from corporate nuget repository?
After trying a few fixes, I realized that:
1. They work. Roslyn really adds attributes, compiles the result.
2. The algorithm is quite simple: you need to find the required * Syntax element, then use the SyntaxFactory to create the correct one and call ReplaceNode.
3. The correct Code Fix is not as simple as it seems at first glance. And instead of offering a problem solution, it is better to ask the programmer to make a correction on their own.
To test the analyzer, simply install the Nuget package (that is, enter the command in the Package Manager Console: Install-Package NullCheckAnalyzer). However, most likely, the package that you have assembled will not work, because initially PowerShell scripts contain an error: for some reason, the “C #” subfolder is added to the path to the dll with the analyzer. Therefore, these lines are better to change
as done here . After these actions, the nuget package is ready, you can upload it to nuget.org, and then add it to the project.
And this is how it looks in Microsoft Visual Studio 2015:

As a result, at the output we get the extension:
1. Which is connected and updated via Nuget
2. Checks the code in the process of writing and compiling (including without MSVS)
3. It is written so simply and quickly that the average pull-request review in the company will take longer
And finally, a couple of tips:
1. As you can see, Microsoft preferred the immutable types. And because most of the constructions Code Fix, you can create in advance, and then just give links.
2. In the process of testing, you can easily check only one file, and therefore it is better to specially provide for options with partial-classes and other multi-file constructions.
3. While there is no release version of Roslyn, but because the API may change slightly. Already,
some Stackoverflow replies contain tips on an obsolete API.
4. Ordos prompted a
page on the Internet with a similar description .