- Published on
Brief intro into JetBrains.Annotations
- Authors
-
-
- Name
- David Mohundro
- Bluesky
- @david.mohundro.com
-
I’m a longtime fan of JetBrains tooling… I’ve been using them since at least 2007 (see my first post about TDD where I first mention ReSharper). Today, my primary editor is JetBrains Rider (or some other flavor of JetBrains IDE depending on the project and stack) on MacOS. I wanted to share about the JetBrains.Annotations
NuGet package.
Per the README:
ReSharper Annotations help reduce false positive warnings, explicitly declare purity and nullability in your code, deal with implicit usages of types and members, support special semantics of the APIs in ASP.NET and XAML frameworks and otherwise increase the accuracy of the code inspections in JetBrains .NET IDEs.
Here’s how I’d say it… the package provides a collection of .NET attributes that you can use to decorate your code and communicate intent specifically to your IDE about how the code should be used.
For example, Rider can track usages across your system; however, if you access an interface via reflection, it can’t tell via static analysis. But if you add the UsedImplicitly
attribute, you’re letting Rider/ReSharper know that the interface is used and to not mark it as safe to remove.
[UsedImplicitly]public interface IFoo { }
A few that I just recently learned about are the AspMvc* attributes (e.g. AspMvcActionAttribute
, AspMvcAreaAttribute
, AspMvcControllerAttribute
, etc.).
Here’s a recent example of usage that I brought in:
[HtmlTargetElement("li", Attributes = "is-selected-area")][HtmlTargetElement("ul", Attributes = "is-selected-area")]public class IsSelectedTagHelper : TagHelper{ [AspMvcController] [HtmlAttributeName("is-selected-controller")] public string? Controller { get; set; }
[AspMvcController] [HtmlAttributeName("is-selected-controllers")] public string[] Controllers { get; set; } = [];
[AspMvcAction] [HtmlAttributeName("is-selected-action")] public string? Action { get; set; }
[AspMvcArea] [HtmlAttributeName("is-selected-area")] public string? Area { get; set; }
[AspMvcAction] [HtmlAttributeName("is-selected-actions")] public string[] Actions { get; set; } = [];
[HtmlAttributeName("is-selected-css-class")] public string CssClass { get; set; } = "active";
[ViewContext] [HtmlAttributeNotBound] public ViewContext ViewContext { get; set; } = null!;
public override void Process(TagHelperContext context, TagHelperOutput output) { var currentController = ViewContext.RouteData.Values["controller"] as string; var currentAction = ViewContext.RouteData.Values["action"] as string; var currentArea = ViewContext.RouteData.Values["area"] as string;
if ( (!string.IsNullOrEmpty(Controller) || Controllers.Any()) && !( ( !string.IsNullOrEmpty(Controller) && Controller.Equals(currentController, StringComparison.OrdinalIgnoreCase) ) || Controllers.Contains(currentController, StringComparer.OrdinalIgnoreCase) ) ) { return; }
var areaToCompare = Area; var currentAreaToCompare = currentArea;
if (areaToCompare == "Dashboard" && currentAreaToCompare == null) { areaToCompare = ""; currentAreaToCompare = ""; }
if ( !string.IsNullOrEmpty(Area) && !(areaToCompare ?? string.Empty).Equals( currentAreaToCompare ?? string.Empty, StringComparison.OrdinalIgnoreCase ) ) { return; }
var actions = new List<string>(); if (!string.IsNullOrEmpty(Action)) { actions.Add(Action); }
actions.AddRange(Actions);
if ( actions.Any() && ( string.IsNullOrEmpty(currentAction) || !actions.Contains(currentAction, StringComparer.OrdinalIgnoreCase) ) ) { return; }
var existingClasses = output .Attributes.FirstOrDefault(a => a.Name == "class") ?.Value.ToString(); var newClasses = string.IsNullOrEmpty(existingClasses) ? CssClass : $"{existingClasses} {CssClass}"; output.Attributes.SetAttribute("class", newClasses); }}
And then usage is like this:
<li is-selected-controller="Users" is-selected-area="Settings"> <a asp-area="Settings" asp-controller="Users" asp-action="Index">Users</a></li>
What’s great, though, is that Rider can now intelligently complete the areas, controllers or actions, because it knows the purpose for this property… instead of just telling me it is a string
, it knows it is a string
that represents an MVC Area
or similar.
You can see a full list of the attributes up at https://www.jetbrains.com/help/resharper/Reference__Code_Annotation_Attributes.html.