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 inspection for null

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:

Navigation features for MVC actions

And the advanced support for  INotifyPropertyChanged in various UI frameworks is also enabled by annotations:

Extra alt+enter menu items for INotifyPropertyChanged

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.

Regular expression functionality due to method with annotation

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.

ReSharper annotations options page

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.

Options for copying source

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.

Summary

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.

Usage

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.

Licensing

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.

This entry was posted in How-To's, ReSharper Tips&Tricks and tagged , , . Bookmark the permalink.

12 Responses to How to use JetBrains Annotations to improve ReSharper inspections

  1. Marcelo says:

    It would be useful to have a way to export the annotations as XML from actual source (via some tool in resharper?). It that way, I can generate a nuget of my library, have the code annotated and doesn’t have to compile the annotations as internal.

    • Matt Ellis says:

      There actually is an internal only feature that can do this. It’s really intended as a development aid for the annotations feature, rather than an end user feature, since it doesn’t really fit into the scenarios we expect for annotations.

      Typically, an external annotations file is for when you don’t have access to the source of the assembly, and have no option but to add the annotations through an external file. The downside of this approach is that the file has to be distributed somehow, either named appropriately and sitting next to the assembly (and this would mean adding it to the nuget package), or distributing as part of an extension (which is a separate install to the assembly itself). It’s also an extra step in the build process.

      On the other hand, if you’ve got access to the source of the assembly, you can easily add the annotations (as source, internal) and compile it all in. The annotations then flow with the assembly, with only a little overhead. We think this is a simpler and less error prone scenario.

      That said, it’d be really useful to hear your reasons for preferring an external annotations file.

      • Muir says:

        If I were to use the annotations I would also prefer to distribute the annotations as a separate XML file. I already distribute XML documentation files together with product DLL’s, so one more won’t make much difference, and it makes me uncomfortable including third party code in this part of my product.

        As an example of why, your license agreement, clause 5, provides me a license to distribute the annotations, but it does not provide my customers a license to redistribute your annotations as part of their product. This is an essential use of my product.

        Further the license agreement clause 7 states that I must agree to the third party license agreements, of which there is a massive list. I would need to go through this list and determine if any of these license agreements applied to the annotations, and if they did I would need to then add it to my products license agreement.

        Realistically I can’t do any of this, it’s too much work and I might get it wrong. I also wonder what else I’ve missed that I might discover later. It’s much easier to not use the annotations.

        It also looks like my customers have no rights to redistribute the XML annotations file, and my current license agreements with them may not cover this either. I can’t afford to renegotiate my license agreements.

      • Steven Bone says:

        Like Muir, I would like to be able to export external annotations along with my compiled dll and standard IntelliSense XML files, whilc using the annotations from NuGet. The internal feature you are referring to would be great to have exposed. Using the internal method as you describe doesn’t have the same cleanliness to it.

  2. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1924

  3. Bart Koelman says:

    This is all great, but most of the time I forget to add these annotations while writing code. To overcome this, I made a VS extension that shows me all class members that have not yet been annotated.

    It can be installed per-solution as a NuGet package or as a VS2015 extension. Feel free to try it out at https://github.com/bkoelman/ResharperCodeContractNullability, it’s open source.

  4. Is it possible to tell via annotations that the returned value of a method should not be ignored by the caller?
    I think I saw such a warning on one of the .NET framework methods. Since I have many immutable types which have “modifier” methods that return a new instance that contains the modifications. A common error is to call i.e.
    immutableThingy.ChangeXY(77);
    So the correct call would be:
    immutableThingy = immutableThingy.ChangeXY(77);
    If ChangeXY(int) has been annotated ReSharper could check and warn on the incorrect call.

    • Matt Ellis says:

      You can use the [Pure] annotation for this. Strictly speaking, it’s intended for methods that do not change the state of the object they’re defined on, and can be used to reason about that – the method is pure, and has no side effects on the owning object.

      But ReSharper has an inspection to make sure the return value is used – if the method has no side effects, the only useful thing it can do is return something, so you really should use it.

      We’ve talked about adding a new attribute that is semantically different, but has the same effect, for working with methods that aren’t strictly pure, but require the same inspection on the return value. This hasn’t been implemented yet, but you can vote on it here: RSRP-438115

  5. Would it make sense to mark the nuget package with the developmentDependency attribute?
    I have created multiple packages which references the JetBrains.Annotations packages, and then manually modified the packages.config file according to: http://docs.nuget.org/consume/command-line-reference#excluding-development-dependencies-when-creating-packages

    It works without any problems as far as I can tell.

    • Matt Ellis says:

      That sounds like a nice idea, although it would require removing if you do enable the JETBRAINS_ANNOTATIONS define to include the dependency (this would need testing, I haven’t played with developmentDependency much). I’ve added a feature request: RSRP-453045.

  6. Edward Benson says:

    I am using the “Include the source” option: “This scenario best targets third-party frameworks.”

    “Typically, if you wish to distribute an assembly with annotations, you SHOULD NOT make the annotations conditional, and SHOULD mark them as internal.”

    Since this is the recommended option, it would be much better if “Make annotations Internal” checkbox is checked by default. Currently this checkbox is not checked by default, and it’s very easy to miss. This would avoid the pain of ambiguous reference errors.

Leave a Reply

Your email address will not be published. Required fields are marked *