How to use JetBrains Annotations to improve ReSharper inspections
ReSharper’s analyses and inspections are already very smart, finding code smells, dead code, and potential runtime issues directly from your source code. They build abstract syntax trees, create a semantic model of your code, and construct control flow graphs for tracking values, especially nullness.
But we can make them smarter through the use of annotations.
If we know that a parameter to a called method should never be null, ReSharper can warn you to check for null before calling the method. Conversely, if we know that a method will never return null, ReSharper can warn you that any null checks on the result are redundant, and help you safely remove the redundant code. Annotations can tell us when items in a list can be null, or will never be null, or when a method is an assertion method, and any code after that method call is redundant due to the assert throwing an exception.
ReSharper even uses annotations to provide extra functionality. For example, string format argument matching and regular expression syntax highlighting are enabled on methods marked with annotations, as are navigation to MVC controllers and actions:
And the advanced support for INotifyPropertyChanged in various UI frameworks is also enabled by annotations:
So what are these annotations and how do we use them?
The good news is that the development team has already done the hard work of annotating the whole of the Base Class Library for you, so you don’t need to do anything in order to get this support when working with standard .NET Framework classes and methods.
Furthermore, extensions can also define “external annotations” for pre-compiled assemblies, such as telling ReSharper that xunit.net‘s Assert methods can result in dead code, or to add regular expression highlighting for Assert.Matches.
Even better, we can use these annotations in our own code, and improve ReSharper’s analyses and inspections when working with our own types and methods. We can even include the annotations when distributing our code, so consumers of our libraries get better support in ReSharper.
Annotations are implemented with .NET attributes, so it’s very easy to add to your own source code. When running an inspection or looking to activate a feature, ReSharper will look at method calls, properties and so on, and look to see if any annotation attributes are applied to the method, parameters or return value, and use that annotation to help improve the accuracy and usefulness of the inspection or feature.
In order to use the annotation attributes, you need to include them in your project. There are several ways of doing this, targeting different usage scenarios.
The JetBrains.Annotations NuGet package
The simplest way to include the annotations is to add a reference to the official JetBrains.Annotations NuGet package. This will add a binary dependency on the JetBrains.Annotations.dll assembly, which includes all of the attributes in the JetBrains.Annotations namespace. Once added as a reference, your source code can simply use the attributes directly.
The most obvious concern here is in adding a binary reference to a third party assembly. Many developers don’t wish to add references if they can avoid it, especially not to a library that is there solely to aid source code analysis, no matter how helpful it is. Why should I deploy JetBrains.Annotations.dll to production?
Of course, the ReSharper team have already thought of this, and make use of a little-known feature of .NET attributes. All of the annotation attributes are marked with the [Conditional(“JETBRAINS_ANNOTATIONS”)] attribute. This is a special attribute that the compiler understands, and can be applied to all sorts of types, methods, properties, and, surprisingly, attributes. If the string argument to the annotation is not defined as a compiler symbol in your project properties, the target of the [Conditional] attribute isn’t compiled into the resulting assembly. For methods, this means any calls to that method are ignored. When applied to attributes, it means the attribute itself doesn’t get applied to the compiled target method, class, etc.
So by default, using the attributes referenced from the JetBrains.Annotations NuGet package does nothing at compile time – the resulting compiled assembly does not include a reference to JetBrains.Annotations.dll, and the attributes are not applied to any classes, methods, properties, parameters and so on. However, the attributes are still visible in the source code, and ReSharper can make use of them.
Since the annotations are not included in the final compiled assembly, this method is best suited to applications, where no one else is expected to consume the code – there is no-one who would benefit from having the annotations distributed alongside the app.
Alternatively, if your assembly is compiled with the JETBRAINS_ANNOTATIONS symbol defined, the attribute usages are compiled into the resulting assembly, as is the dependency on JetBrains.Annotations.dll – the dll must be distributed with the compiled assembly. While not the most typical scenario, it can be still be useful if creating a framework for internal consumption, where distributing or deploying the JetBrains.Annotation.dll is not an issue.
Including the source
This scenario best targets third-party frameworks. If your code is to be distributed as a NuGet package that is to be consumed by anyone, it’s not appropriate to include a binary dependency on JetBrains.Annotations.dll. It’s entirely possible that not all consumers will be using ReSharper, and the dependency becomes unnecessary, and unwanted. And using the JetBrains.Annotations package without defining the JETBRAINS_ANNOTATIONS symbol means that your consumers don’t get the benefit of the annotations that you’ve defined.
If you are distributing code for external reuse, you can include the source of the attributes directly in your project. ReSharper doesn’t mind where the attributes are defined – in JetBrains.Annotations.dll, in an external XML file, or in your own code. By default, it simply looks for attributes defined in the JetBrains.Annotations namespace. As long as the attributes exist and are applied to the code, ReSharper will use them for improved inspections and features.
To get a copy of the code, simply open the ReSharper Options dialog and navigate to the Code Annotations page. From here you can easily add a reference to the JetBrains.Annotations NuGet package, or you can copy the source of the annotations attributes to the clipboard. Once copied to the clipboard, you can paste into a new file, and the annotations are now available to be used in your own code.
There are two options when copying the code: one to make the annotations conditional, and another to generate the attributes as internal (as opposed to public).
Typically, if you wish to distribute an assembly with annotations, you SHOULD NOT make the annotations conditional, and SHOULD mark them as internal.
By making them conditional, the source is created with the [Conditional(“JETBRAINS_ANNOTATIONS”]) attribute, and the annotations will not appear in the compiled assembly unless you define the JETBRAINS_ANNOTATIONS symbol during compilation. There is little benefit in doing this over simply referencing the JetBrains.Annotations NuGet package.
Marking the annotations as internal means that the classes are only visible to the assembly they are defined in. This is the recommended setting. If you make them public, the attributes can be used by any consumer of the assembly – they effectively become part of your published API! If more than one assembly exposes them as public, any consumer that wishes to use annotations now has a potential for ambiguous reference errors, as there will be more than one JetBrains.Annotations namespace declaring public attributes with the same names.
The only real reason for using public is to allow sharing a single implementation of the attributes in a solution that is not going to be consumed by third parties. Again, using the JetBrains.Annotations package makes this simpler and easier.
To summarize, if you want to add annotations to your solution or project, this is what you should do:
To use annotations in an application that no-one will reference as code: Include a reference to the JetBrains.Annotations package, and DO NOT define the JETBRAINS_ANNOTATIONS symbol. The JetBrains.Annotations.dll reference is design time only, and does not need need to be deployed with your application.
To use annotations in an assembly that is to be used by third parties: Include the attributes as source. DO define as internal, DO NOT generate as conditional. Both you and third party consumers will benefit from the annotations.
To annotate a pre-compiled assembly: Create an external annotations XML file, and distribute as an extension.
Update: As of ReSharper 2016.1, ReSharper’s own collection of external annotations XML files has been open sourced, and accepts pull requests, which means a separate extension to distribute your own external annotations is no longer necessary. Please see this blog post for more details.
Of course, now you’ve got the annotations referenced, what attributes are there, and how do you use them? ReSharper’s excellent help site provides details on using the annotations, and provides a full reference of all of the attributes that are available.
Finally, if anyone is worried about distributing or reusing the annotations package or source code, you’ll be pleased to know that both the package and the source code are explicitly covered in section 5 of ReSharper’s license agreement, which allows you to redistribute the annotations, either in source or binary form.