Improving Sitecore code quality with ReSharper External Annotations

I guess most of us Sitecore developers are familiar with the JetBrains ReSharper plugin for Visual Studio. The tool actually made me accept moving from the Java/IntelliJ IDEA world to the .Net/Visual Studio world many many years ago and I’m still on the Idea keyboard shortcut scheme.

Besides all the nice refactoring tools, code hints etc that comes with ReSharper, it also comes with a framework for annotating code with attributes. One can argue if this should be used or not in your own code, but it opens for a really nice way for improving code quality when working with external libraries.

As Sitecore has grown over the years, the API becomes larger and larger and there are sometimes multiple ways of achieving the same thing. Sometimes the API is a bit ambiguous to new developers and some operations should be avoided from a performance perspective etc. With ReSharper External Annotations we can give developers code hints and feedback directly in Visual Studio when using the Sitecore API in a way that may not be intended.

With external annotations, you can describe 3rd party assemblies, such as the Sitecore.Kernel.dll, and keep it along your project source code. Below are some examples to get started and some ideas of what it can be used for. All the details about External Annotations is well documented in the ReSharper documentation.

First you need to create a folder called “ExternalAnnotations” in your solution root folder. In that folder you create an XML file with the same name as the DLL you want to annotate, for example “Sitecore.Kernel.dll”. The format of the XML file is essentially the same as the XML document files, so copying elements from an existing file is of good help here.

So why is this useful?

Well, here are a few examples that I think both explains the benefit and how to do it. Let’s create an external annotation file for Sitecore Kernel. Name it Sitecore.Kernel.xml and put it in the ./ExternalAnnotations folder.

<?xml version="1.0" encoding="utf-8" ?>
<assembly name="Sitecore.Kernel">
  <member name="P:Sitecore.Data.Items.Item.Languages">
    <attribute ctor="M:System.ObsoleteAttribute.#ctor(System.String,System.Boolean)">
      <argument>This method returns ALL installed languages in the system. Not the available languages on the actual item. Use the LanguageManager to get all languages</argument>
      <argument>true</argument>
    </attribute>
  </member>
</assembly>

Deprecated PropertySo this little piece of annotation will add the [Obsolete] attribute to the Languages property on the Item class. If using this property, ReSharper would flag this as an error in the Visual Studio editing environment.

The reason for flagging the Languages property is that many developers tend to assume it gives a list of languages applicable to the current item. In reality it gives a list of all available languages in that database, regardless if there are language versions or not on the current item. I’ve seen this error many times – even in code written by Sitecore.

Note: When adding annotations like this, we only change how ReSharper interprets the code. It will not affect the annotated dll itself, the compilation or the output.

Another example is null checking of returned values. Let’s say you want to encourage developers to null check the returned object of GetItem. We can do this by adding the [CanBeNull] attribute to the various GetItem version, like this:

  <member name="M:Sitecore.Data.Database.GetItem(Sitecore.Data.ID)">
    <attribute ctor="M:JetBrains.Annotations.CanBeNullAttribute.#ctor"/>
  </member>
  <member name="M:Sitecore.Data.Database.GetItem(Sitecore.Data.ID,Sitecore.Globalization.Language)">
    <attribute ctor="M:JetBrains.Annotations.CanBeNullAttribute.#ctor"/>
  </member>
  <member name="M:Sitecore.Data.Database.GetItem(Sitecore.Data.ID,Sitecore.Globalization.Language,Sitecore.Data.Version)">
    <attribute ctor="M:JetBrains.Annotations.CanBeNullAttribute.#ctor"/>
  </member>

null check itemsThere are more versions of this method, but get the idea. By adding this attribute, ReSharper will mark potential null exceptions as it now knows that GetItem may return null.

Note: When changing the annotation XML files, you need to reload the solution in order for ReSharper to pick up the changes.

Another useful annotation could be to mark the ItemAxes.GetDescendant methods, as those may be very heavy on large/deep item trees. We can mark those with a message asking developers to consider using Content Search instead.

We can also turn this the other way around. You might have seen ReSharper complaining about missing null checks in methods, where such exception will never occur, because you’ve already called the Sitecore Assert methods. With External Annotations, we can mark these assert methods as [AssertionMethod], meaning that ReSharper understands that code execution will halt if the assertion fails.

A larger example annotation file of the described examples above is available on Gist.