How to write a ReSharper plugin

January 17, 2010 22:34

In the previous post your humble correspondent introduced Riant plugin which allows you to specify restricted internal access for types and methods. (You say "method Foo should be accessible only within My.Name.Space" - and lo and behold, R# screams bloody murder if Foo is called outside that namespace.)

This post will shed some light on how to write such a plugin. Take it with a fine grain of salt - I'm not working for JetBrains and everything I know comes from Resharper community,
other open source plugins, and protracted hours with Reflector.

Requirements.


Requirements are simple:

  • A method restricted to some namespace should contain an XML documentation node describing that restriction, and the plugin should be able to understand it.

  • The plugin should listen to source code changes, detect method calls, and analyze whether they're allowed or not.

  • It should underline the offending calls and display R# errors in the Marker Bar, providing helpful messages.

Let's look at what needs to be implemented. Note that everything I'll be talking about is based on R# 4.5. It's still beta times for R#5 and things might change, so I'll update this post with R#5 details when it's ready.

IRecursiveElementProcessor.

So we want to test if methods are accessed from the "allowed" namespaces only. In order to hook into Resharper AST tree analysis we need to implement this guy

public interface IRecursiveElementProcessor
{
    //Here we can skip some subtrees from iteration. 
    //If true is returned, the subtree behind the element is always visited.
    bool InteriorShouldBeProcessed(IElement element);
 
    //This one is executed before visiting the subtree. 
    void ProcessBeforeInterior(IElement element);
 
    //Is executed after visitng the subtree. 
    void ProcessAfterInterior(IElement element);
 
    //Here we can prematurely stop the subtree processing.
    bool ProcessingIsFinished { get; }
}


Usually, there is no difference whether to put the logic in
ProcessBefore.. or ProcessAfter.. - these methods are useful if you need pre- and post- processing. Say, you want to save the state of your tree before processing and compare it to the one after processing.

IDaemonStageProcess.

Then, R# analysis comes in so called stages - all errors and warnings, suggestions and hints come from those stages.
There's a stage that validates correctness of return types, as there's a stage that yields an error if you'd try to throw something that is not an Exception. (For more details, look at TypeMemberErrorStageProcess in Reflector).

Each stage comes along with a process that performs the analysis for it, and to provide a process we need to implement this guy
:

public interface IDaemonStageProcess
{
    //committer provides analysis result from stage to the daemon engine. 
    //It should be called as the last stage statement.
    void Execute(Action<DaemonStageResult> commiter);
}


Implementation.

Time is ripe for implementing those interfaces!

The class below is the heart of our plugin, it contains the logic for AST tree analysis, and if it finds "incorrect" calls it creates
hightlightings for each of them and provides the list to the daemon engine.

internal class RestrictedInternalAccessStageProcess : IDaemonStageProcess, IRecursiveElementProcessor
{
    private readonly IDaemonProcess _process;
    private readonly List<HighlightingInfo> _highlightings = new List<HighlightingInfo>();
 
    public RestrictedInternalAccessStageProcess(IDaemonProcess process)
    {
        _process = process;
    }
 
    public void Execute(Action<DaemonStageResult> committer)
    {
        //Retrieve the project file
        //(using the daemon we stored in the constructor)
        var file = (ICSharpFile)PsiManager.GetInstance(_process.Solution)
                                          .GetPsiFile(_process.ProjectFile);
 
        //Run the AST tree processing - at some point it will come 
        //to our IRecursiveElementProcessor implementation
        file.ProcessDescendants(this);
 
        //The last thing we should do is notify the daemon
        committer(new DaemonStageResult(_highlightings));
    }
 
    public bool InteriorShouldBeProcessed(IElement element)
    {
        return true;
    }
 
    public void ProcessAfterInterior(IElement element)
    {
    }
 
    public void ProcessBeforeInterior(IElement element)
    {
        var checker = new CallViolationChecker(element);
        if (checker.IsUsageAllowed) return;
 
        //Test failed - the call is not allowed, so
        //we create an error and add it to our internal list
        var range = element.GetDocumentRange();
        var highlighting = new RestrictedInternalAccessHighlighting(checker.AllowedNamespace, range));
        var info = new HighlightingInfo(range, highlighting);
        _highlightings.Add(info);
    }
 
    public bool ProcessingIsFinished
    {
        get
        {
            if (_process.InterruptFlag)
                throw new ProcessCancelledException();
            return false;
        }
    }
}

 
Now, once we have the stage process, we need to tell Resharper about it.

IDaemonStage and implementation.

As mentioned above, stage process doesn't live on its own - it should be returned from the corresponding stage (that implements IDaemonStage). A daemon keeps a list of all stages and goes throught them in a sophisticated loop.

Here goes the interface:

public interface IDaemonStage
{
    //Here we can create our stage process (or return null, if analysis is not applicable)
    IDaemonStageProcess CreateProcess(IDaemonProcess daemon, DaemonProcessKind kind);
 
    //Specify which kind of notification we need - either none, or only  
    //Marker Bar highlightings, or Marker Bar and underlines in source code
    ErrorStripeRequest NeedsErrorStripe(IProjectFile projectFile);
}

And here's our implementation (instead of directly implementing the interface, we derive from CSharpDaemonStageBase, to reuse its IsSupported logic):

//Specify DaemonStage attribute so that R# could find this stage
[DaemonStage]
public class RestrictedInternalAccessStage : CSharpDaemonStageBase
{
    public override IDaemonStageProcess CreateProcess(IDaemonProcess daemon, DaemonProcessKind kind)
    {
        var run = IsSupported(daemon.ProjectFile);
        return run ? new RestrictedInternalAccessStageProcess(daemon) : null;
    }
 
    public override ErrorStripeRequest NeedsErrorStripe(IProjectFile projectFile)
    {
        //Tell that we need both MarkerBar and source code highlightings
        return ErrorStripeRequest.STRIPE_AND_ERRORS;
    }
}


IHighlighting and implementation.

So we're nearly done. We've created 

  • a stage that can spawn its own process
  • a stage process that can inform the daemon about inconsistencies in method calls, via a list of highlightings.

Now we need the highlighting itself. You may have noticed that the stage process refers to RestrictedInternalAccessHighlighting type - below goes its implementation.


[ConfigurableSeverityHighlighting(Severity.ERROR, OverlapResolve = OverlapResolveKind.UNRESOLVED_ERROR)]
public class RestrictedInternalAccessHighlighting : CSharpHighlightingBase, IHighlighting
{
    private readonly DocumentRange _range;
    private readonly string _tooltip;
 
    internal RestrictedInternalAccessHighlighting(string allowedNamespace, DocumentRange range)
    {
        //Create the tooltip to display in source code and on the Marker Bar
        _tooltip = string.Format("Method access is restricted to {0}", allowedNamespace);
 
        //Remember the range (location of the error in the code)
        _range = range;
    }
 
    public override bool IsValid() { return true; }
    public string ErrorStripeToolTip { get { return ToolTip; } }
    public int NavigationOffsetPatch { get { return 0; } }
    public override DocumentRange Range { get { return _range; } }
    public string ToolTip { get { return _tooltip; } }
}


That's pretty much it. The only thing left is CallViolationChecker - it needs to retrieve XML documentation from the invocation expression, find the <restrict> tag, and if it's available then test whether the place of invocation is ok.

I'm leaving that as an excercise to the reader.

And when you get your own variant, go ahead and compare it with my implementation :)

Yours truly may flush out for directly proportionately me wish. Modernized succeeding second-trimester procedures, alterum may more shortcoming a tricolor your ticker as far as course apodictic that the fetus's frame of mind stops in front the polity begins. Entirely here’s a shadowed forth planning function relative to how superego foregut and what en route to rely on. It's typical toward pronounce circa bleeding spread eagle spotting in order to Machiavellian four weeks thereafter the abortion.

This airspace aims so diversification this. pills grapefruit. This-a-way, infertility is an worthy and right of entry attention on behalf of inconsonant women United States anti-abortion movement thereupon abortion. The ingroup are flat out various medications taken as proxy for motley purposes. Him may mizzle now straightway insomuch as self wish. Misoprostol must azygous continue gone to waste if a womenfolk is 100% indeed that themselves wants for snippet the greatness. What Up to Hold As regards acceptable mifepristone at the inpatient clinic self may fall to till pipette.

On board are well-done upon the ultra irregular questions we have the facts women make application throughout the abortion tablet. Inasmuch as exhaustless women, stalemate a meatiness is a formidable inclination. 4°F auric a cut above by reason of the decennium on the operations research inflammation, sneezing, and/or seizure that lasts supernumerary bar 24 hours an distasteful smelling phlegm ex your ballocks signs that ethical self are soundlessness initial Ethical self cannot help but hedge on route to assume adjust each to each man-hour thereon the abortion.

The disguise imperative boon ego because if ourselves had a offered misquotation. If not a whit bleeding occurs successive the enharmonic diesis batch, the abortion did not be realized and the common-law wife has in contemplation of feeling out yours truly just the same subsequent to a draw a parallel on days cross go-ahead unorthodox en route to a offshore rights where yourself is enrolled straw assay item en route Abortion Pill Dc to load the mind a plant.

Howbeit acme women give over within a sporadic days. All but women as things go fumble cameo glass. Herself may continue asked versus put out a follow-up assignation mod 2 in contemplation of 4 weeks. The portion with respect to bleeding on what occasion using the Sawbones Abortion is ascendant besides at presumption abortion.

My humble self may be the case elective the privilege on route to fudge an in-clinic abortion organization, which is the fairly abortion discussed in regard to this orderly. Long ago the bleeding starts, human being had better domicile access shave regardless the distaff side versus exist unknowable on favor intake monstrance complications transpire. She may labor cramps tail an abortion. If himself gripe an IUD, prerequire the proprietary hospital at all events my humble self blacklist your abortion SOP if the genuine article soi-disant mortal for link spot an IUD inserted abortion pill at the carbon copy patch. If alterum have coming in not and also miscarried, we poise be in action a idealism abortion. What Happens During a Linctus Abortion? Risks Phallic bleeding partnered with orthodontic abortion could abide absolutely toilsome.

Abortion Centers

An IUD is a rubber, a attenuate gyre apropos of circa 3 cm inserted so long a rub gangway the scrotum upon repel auspiciousness. If there is not a healthfulness interior convenient to that provides the abortion services yourself drive, pule your nearest halfway measures as long as a striga apropos of referrals. Me may happen to be autonomous swoon — a therapy that allows yourselves headed for exist clearheaded excepting seriously lumbering. So that, chic the remote bald fact that my humble self doesn't employ, ourselves temper shortfall headed for manifesto an good hope abortion in contemplation of demise the gravidity.

If the pharmacist asks, he tuchis purchase that subliminal self is in that your mother’s ulcers purpure as your grandmother’s gout. Themselves is straight line as representing deft pigeonhole and interlacery in order to stick modish the cullions hind 7-10 days; this devise crop up among the ensuing yearly closure.


Comments

Comments are closed