.NET Tools
Essential productivity kit for .NET and game developers
Open sourcing ReSharper’s external annotations
We recently open sourced the external annotations that ReSharper uses to improve analysis and provide extra features over third party code, and we’ve also opened up to contributions from the community. You can now add other assemblies, fix issues with the existing annotations, or simply make use of the information in the files.
But wait, what are external annotations?
ReSharper can use attributes defined in the JetBrains.Annotations nuget package to improve its own analyses and inspections – this blog post has more details. They can give ReSharper extra context to allow for more accurate inspections.
For example, if a parameter or return value can never return null, ReSharper can tell you that your null checks are irrelevant, and provide quick-fixes to remove them. Or perhaps you have a custom attribute that indicates that the method it’s applied to is only called by reflection – ReSharper will normally mark the method as unused, unless the custom attribute is itself marked with the [MeansImplicitUse], in which case ReSharper will mark it as in use.
Annotations are not just limited to improving inspections. For example, the [StringFormatMethod] attribute marks a method as having semantics similar to string.Format. When ReSharper sees this, it will treat the specified argument as a format pattern, and ensure that the right number of parameters are specified in the rest of the method call, and highlight the appropriate argument when the text caret is on the corresponding {0} format specifier.
Similarly, the [RegexPattern] attribute will mark a parameter as being a regular expression, and any string literal passed in will be syntax highlighted, have auto completion and Alt+Enter quick-fixes to test the regex.
There are many more annotation attributes, and you can find out more details in the ReSharper web help, along with a reference for the available attributes.
These attributes are great for adding to your own source code (and they don’t even add any binary references!) – but what about pre-compiled assemblies? How to add these attributes when the code is already compiled? Well, that’s exactly what external annotations do.
An external annotations file is an XML file that ReSharper reads to effectively “add” attributes to existing compiled code. Each file describes types and type members in an assembly, and provides the fully qualified name for attributes to apply, as well as arguments for the attribute constructor (remember, when compiled, attribute constructor arguments are simply constants, serialised into the assembly).
When ReSharper reads the annotations file, it merges these “fake” attributes from the XML file with the real attributes from the assembly itself. As far as ReSharper is concerned, it’s as though the assembly was compiled with these attributes all along! This allows us to add [NotNull] and [CanBeNull] or [RegexPattern] attributes to a compiled assembly, and improve ReSharper’s analysis and feature set for existing code.
The external annotations that ship with ReSharper cover the entire BCL of the .NET framework, and more. We have performed null analysis on the whole BCL, and the results are applied to assemblies, types and type members. On top of that, we have added extra annotations for extra functionality – string format methods, regular expressions, ASP.NET Views, Actions and Controllers, and so on.
For the most part, the annotations are for BCL and related Microsoft assemblies, such as ASP.NET MVC, but certain third party assemblies are also annotated. Primarily, these are commonly used assemblies that benefit from a piece of functionality that the annotations can add. For example, various community MVVM frameworks are annotated with the [NotifyPropertyChangedInvocator] attribute to provide improved INotifyPropertyChanged support. Similarly, NUnit and xUnit.net are marked to contain assert methods, which ReSharper can use to help control flow analysis (e.g. if an assert is always going to cause an exception, we can mark the rest of the method as dead code).
And now, these annotations are open source. They’re available on GitHub, with an MIT license. We use this repo to build the annotations that ship with each version of ReSharper, and we’re opening it up to contributions from the community (we’ve already accepted a pull request to add support for FakeItEasy). If you see a mistake with ReSharper’s null analysis, or missing string.Format highlighting, raise an issue, or file a pull request. If you’d like to add a new third party assembly, fork the repo and add it!
And the best bit? Since version 10, ReSharper will automatically keep the annotations up to date. ReSharper 9 shipped them as a bundled plugin, and will prompt you to update them. This allows us to update some functionality without waiting for a full ReSharper release (as an example, an ASP.NET MVC update caused ReSharper’s navigation between views and controllers to stop working. The fix was to update the annotations to support the new version of the assembly).
But what this means now is that if a fix or new assembly is contributed, we can push the new version of the annotations to ReSharper 9, 10 and 2016.1.
Previously, external annotations could be shipped either side-by-side with an assembly (as a file called MyAssembly.ExternalAnnotations.xml) or as part of an extension. This still works, and if you have an existing ReSharper extension that adds features via a plugin .dll, it’s still a good idea to ship the annotations with the extension. But if the extension is just annotations, then it can be added to the standard annotations package, and we can make sure that all ReSharper users get it.
Check out this web help page for more details on how external annotations work, and what the file format looks like. This page lists all the annotations that are available. And finally, here’s the GitHub repo.