DevTalk.net

ActiveMesa's software development blog. MathSharp, R2P, X2C and more!

ReSharper SDK Adventures Part 4 – SSR, Gutter Marks and Suppressions in Agent Mulder

without comments

In the previous three parts of the SDK Adventures we looked at some artificially created samples. Time to change all that. In this part, we’re going to take a look at some of the advanced features used by Igal Tabachnik’s (@hmemcpy) excellent Agent Mulder plugin.

Brief Overview of Agent Mulder

The principal idea behind Agent Mulder is to get ReSharper to support various IoC frameworks. To get a better appreciation for its features, take a look at the screencast or, better yet, download the plugin and try it out for yourself. But to sum things up, Agent Mulder augments ReSharper with intrinsic knowledge about the types registered in the IoC container and allows all sorts of wonderful things, such as e.g. the possibility to navigate to the point where a type is registered.

But seeing how our SDK adventures concern actual implementations of the various features, here’s what we’re going to talk about today:

  • Programmatic use of Structural Search and Replace (SSR)

  • Use of gutter marks

  • Suppression of existing inspections

All of these are fairly advanced topics, but we’ll tread carefully and hopefully things will make sense.

Programmatic SSR

Structured Search and Replace is a ReSharper feature that helps people locate code based on some pattern. For example, you can go off looking for $1.Foo($2) where $1 and $2 have specific types. The Replace part of the SSR lets you then replace the found pattern with something else by presenting a context action.

So how does this work in Agent Mulder? Well, AM needs to locate places where a particular component has been registered. To do that, it creates and uses search patterns – definitions of type IStructuralSearchPattern which are object-orientated equivalents of what one would otherwise define in the SSR GUI. For example, here’s the pattern for a Castle Windsor container registration:

private static readonly IStructuralSearchPattern pattern =
  new CSharpStructuralSearchPattern("$container$.Register($arguments$)",
    new ExpressionPlaceholder("container", "Castle.Windsor.IWindsorContainer", false),
    new ArgumentPlaceholder("arguments", -1, -1)); // any number of arguments

The above is a simple search for $container$.Register($arguments$) where the first parameter has a type of IWindsorContainer and the second argument is actually variadic (i.e., there might be several arguments).

Now that we’ve got the pattern to search with, how do we perform the search? Well, to match an expression to a pattern we need a matcher embodies by the IStructuralMatcher interface. This matcher is effectively created from the defined pattern, for example:

var matcher = pattern.CreateMatcher();

With the matcher in hand, any particular ITreeNode can then be matched against the pattern using a QuickMatch() method. This method takes the tree node as a parameter and returned a bool indicating whether there is, in fact, a match. Keep in mind that a matcher works on a particular in vocation, not on the entire tree. This is why Agent Mulder also tries to get the tree node as an IInvocationExpression and, if successful, performs a match against all its subexpressions.

Gutter Marks

Gutter marks are little glyphs on the left-hand side of the editing pane. They typically show various useful bits of information, such as indicating that a type inherits from another type. Gutter marks are also clickable. In the case of Agent Mulder, a gutter mark is used to indicate that a type is registered in a container.

So what’s a gutter mark in terms of the API? Put simply, it is a class that inherits from IconGutterMark and is registered with an assembly-level RegisterHighlighter attribute. There are a few things the gutter mark class has to do. First of all, it has to call the base class constructor and provide an image that it is going to display:

public ContainerGutterMark()
  : base(ImageLoader.GetImage("Hat", Assembly.GetExecutingAssembly()))
{
}

The above loads the Hat.png image file from the current assembly. In order for the above invocation to work, an additional assembly-level attribute is required in AssemblyInfo.cs:

[assembly: ImagesBase("AgentMulder.ReSharper.Plugin.Resources")]

Now, once the image is loaded, there is also the IsClickable property of the gutter mark class to implement. Its function is predictable: if set to true, the OnClick() function must be defined.

public override bool IsClickable
{
  get { return true; }
}

Now, before we get to clickability itself, we need to discuss the way gutter marks are actually added to a file because, after all, their invocation (the OnClick() override) depends entirely on their position relative to code.

The answer to this question is rather simple: gutter mark positions are actually controlled by highlightings – yes, the same highlightings that are used to indicate warnings, errors, and so on. This exact mechanism allows the clickable gutter mark to actually get information about its location. Here’s how it works: the gutter mark is registered at the assembly level with an attribute similar to the following:

[assembly: RegisterHighlighter("Container Registration", 
  "{B57372C1-16C3-4CB5-8B68-A0FBEFB487AD}", 
  EffectType = EffectType.GUTTER_MARK, GutterMarkType = typeof(ContainerGutterMark), 
  Layer = 2001)]

The critical parameter in the above registration is the Id, and in this case it has a value of "Container Registration". Now, we can go ahead and create a simple highlighting with a severity of INFO. The critical piece of the puzzle is that the AttributeId parameter of the highlighting has to match the Id from above:

[StaticSeverityHighlighting(Severity.INFO, "GutterMarks", 
  OverlapResolve = OverlapResolveKind.NONE, AttributeId = "Container Registration", 
  ShowToolTipInStatusBar = false)]
public sealed class RegisteredByContainerHighlighting : IClickableGutterHighlighting
{
  // implementation here
}

But this doesn’t answer the most difficult of questions: how does the gutter mark know where to go? It knows because the gutter mark’s OnClick() override gets an IHighlighter as a parameter. This means that if we get a solution manager and this highlighter, we can get at the actual highlighting and invoke its own Click() method:

public override void OnClick(IHighlighter highlighter)
{
  ISolution currentSolution = Shell.Instance.GetComponent<ISolutionManager>().CurrentSolution;
  if (currentSolution == null)
  {
    return;
  }
  var clickable = Daemon.GetInstance(currentSolution).GetHighlighting(highlighter)
                  as IClickableGutterHighlighting;
  if (clickable != null)
  {
    clickable.OnClick();
  }
}

Put all of the above together and you get a clickable gutter mark.

Suppression of Inspections

If you turn on Solution-Wide Analysis (SWA), you’ll get container-registered classes as unused, which isn’t good because they are used, albeit in a container. Sure, you could just mark these as [UsedImplicitly], but this is extra work for the developer, so Agent Mulder’s idea is to handle this automatically.

How does it work? Well, one of the daemon processes in ReSharper’s ecosystem is the CollectUsagesStageProcess. This process handles usage information, and conveniently enough, this component has a SetElementState() method that can set the ‘usage state’ of a particular element (in the case of Agent Mulder, an IConstructor):

private void SetConstructorsState(ITypeElement typeElement, UsageState state)
{
  foreach (IConstructor constructor in typeElement.Constructors)
  {
    collectUsagesStageProcess.SetElementState(constructor, state);
  }
}

It really is that simple. Agent Mulder actually sets the usages in its daemon stage process as it goes through the files. This means that, for classes that are registered in the container, constructors are marked as used which prevents SWA from claiming that the class is unused.

Conclusion

I’ve gone over most of the core aspects of Agent Mulder, leaving out perhaps just one – the search & navigation aspect. This really warrants its own entry however, so stay tuned for more R# SDK adventures. Oh, and check out Agent Mulder if you haven’t done so already!

Written by Dmitri Nesteruk

June 19th, 2012 at 10:00 am

Posted in ReSharper