CodeRush – Writing a Context Provider

Published on: Author: Michael

There are two types of developers in the world – those who use CodeRush and those who use ReSharper.  I happen to be in the CodeRush (CR) group for various reasons.  One of the benefits I really like about CR is its flexibility and the ability to easily define my own custom templates.  A template in CR is like a smart code snippet.  When you type a certain key combination in the right (configurable) context then what you type can be replaced by something else.  In this post I’m going to discuss a simple context provider that can be used in CR templates.


Background


Assume you have the following code.


using System;

namespace ConsoleApplication1
{
    class Myclass
    {
        /// <summary>A property.</summary>
        /// <exception cref=”ArgumentOutOfRangeException”>When setting the property and the value is <see langword=”null”/>.</exception>
        public string SomeValue 
        {
            get { return m_value; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException(“value”);
            }
        }

        /// <summary>A method </summary>
        /// <param name=”value”>Some argument.</param>
        /// <exception cref=”ArgumentNullException”><paramref name=”value”/> is <see langword=”null”/>.</exception>
        public void Foo ( string value )
        {
            if (value == null)
                throw new ArgumentNullException(“value”);
        }

        private string m_value;
    }
}

The exception documentation is boilerplate code.  With CR you can easily define a template that says “when I enter /xout while inside an XML doc comment convert it to the desired text”.  Whenever CR sees a template (/xout) it evaluates the context providers, if any, associated with the template.  If the providers say the context is good then the template is replaced with whatever text is specified.  A single template can have multiple contexts defined if desired.  For an exception tag there are three contexts that seem reasonable.


  • Documenting an exception for a property.  The general format is “When setting the property and the value…” because only setters generally throw and the parameter name is always ‘value’.
  • Documenting an exception for a non-property and the parameter name is known.  The general format is “<paramref name=””/>…” where the parameter reference has a parameter name.
  • Documenting a non-property and the parameter name is not know.  The format is the same as above but put the caret inside the reference so the name can be typed in.

The following screenshot shows how this might be done in CR.



The contexts are:


  • HasIdentifier – An identifier is on the clipboard.
  • InXmlDocComment – The current element is inside an XML doc comment.
  • InDocumentationForProperty – The current element is inside an XML doc comment for a property

The InDocumentationForProperty is a custom context I created.  There are several different variants for each of the element types of interest (method, class, etc).  In order to understand how the provider works it is important to have a basic understanding of the CR model.


CodeRush Context Providers


With CR you can define a custom context provider that is called each time a template is evaluated.  The sole purpose of the provider is to determine whether the context is valid or not.  For the custom provider I wrote all I care about is whether the context precedes a specific type of element.


Caveat – the documentation for CR is almost non-existent.  Most of the help you get comes from code samples or posting questions in the forums.  Therefore everything I say is based upon what I understand and should not be taken as final. 


CR breaks up parsed code into elements.  An element could be a namespace declaration, type definition, comment, etc.  An element can have child elements.  For example a class will have a separate element for each member and each member will probably have its own child elements.  Namespace declarations, import statements – they are all elements.  Given an element you can get the next or previous sibling element or the parent of the current element. This comes in handy when you want to figure out where you are at in the code.  Here’s a sample of what the above code looks like to CR.



 


As can be seen the exception tag for the property is a node nested within the larger XML doc comment node.  The parent element has two children: summary and exception.  Exception itself has 3 children: text, see tag and more text (the period).  This information can be visualized using either the Source Tree or Expression Lab windows in CR.  I prefer the Source Tree but they both appear to show the same information.


Writing the Addin


Writing a CR context provider is pretty straightforward. 


  1. Create a new project. 
  2. Select DXCore\VSIX Standard Plugin
  3. In the Settings dialog enter a nice title.  The remaining options can be left at their default settings.

At this point a valid plugin is defined.  For CR providers runs inside a plugin.  The plugin is just a container for the plugins.  For simple providers it is possible to drag and drop the provider from the Toolbox onto the plugin, hook up a couple of provider events and have the plugin do all the work.  But this isn’t very modular so I’m going to create my own context provider class and then add it to the plugin.


  1. Add a new class to the project to represent the context provider.
  2. Add a few namespaces required for CR to work (see the code).
  3. Add a reference to the DevExpress.CodeRush.Extensions assembly to simplify development.
  4. Derive the provider from ContextProvider.
  5. Add any properties that may be useful for configuring the provider.
  6. Implement the IsContextSatisfied virtual method to return true when the context is valid.

The key method for a context provider is the IsContextSatisfied method.  CR calls the method whenever the context needs to be verified.  For this provider the context is satisfied if the current element is inside an XML doc comment and the comment applies to a specific element.  Since I don’t want to write a separate provider for each element type (property, method, etc) I’ll expose a property to allow the element type to be set along with whether the XML doc comment is required or any comment will do. 


public class CommentContextProvider : ContextProvider
{
    #region Public Members

    #region Attributes

    /// <summary>Gets or sets what element the documentation must apply to.</summary>
    public LanguageElementType ForElement { getset; }

    /// <summary>Gets or sets whether the current element must be in a documentation element.</summary>        
    public bool IsDocumentation { getset; }
    #endregion

    /// <summary>Determines if this context is satisfied.</summary>
    /// <param name=”parameters”>The optional parameters.</param>
    /// <returns><see langword=”true”/> if the context is valid or <see langword=”false”/> otherwise.</returns>
    public override bool IsContextSatisfied ( string parameters )
    {
        throw new NotImplementedException();
    }
    #endregion
}

The provider is now operational but it doesn’t do anything useful.  We need to implement the method to detect the cases we care about.  Here’s the core code.


public override bool IsContextSatisfied ( string parameters )
{
    //Make sure we are seeing any changes
    CodeRush.Source.ParseIfTextChanged();

    //No current element
    if (CurrentElement == null)
        return false;
            
    //Verify the current element is a comment of the appropriate type
    if ((IsDocumentation && !InDocumentationElement(CurrentElement)) ||
        (!IsDocumentation && !InCommentElement(CurrentElement)))
        return false;

    return IsCommentFor(ForElement);
}

private LanguageElement CurrentElement
{
    get { return (CodeRush.Source != null) ? CodeRush.Source.Active : null; }
}

private bool InCommentElement ( LanguageElement element )
{
    return (element.ElementType == LanguageElementType.Comment);
}

private bool InDocumentationElement ( LanguageElement element )
{
    switch (element.ElementType)
    {
        case LanguageElementType.XmlDocAttribute:
        case LanguageElementType.XmlDocComment:
        case LanguageElementType.XmlDocElement:
        case LanguageElementType.XmlDocText: return true;
    };

    return false;
}

private bool IsCommentFor ( LanguageElementType elementType )
{            
    //Step back until we get to the root comment element
    var current = CurrentElement;
    while (current != null)
    {
        //If this is a comment or doc comment element then we’ve found the root
        if ((current.ElementType == LanguageElementType.Comment) ||
            (current.ElementType == LanguageElementType.XmlDocComment))
            break;
        else  //Somewhere else so keep going
            current = current.Parent;
    };

    if (current == null)
        return false;

    //Get the next code element and verify its type
    var next = current.NextCodeSibling;
    return next.ElementType == elementType;
}

The first thing the method does is call ParseIfTextChanged to ensure that the current element has been updated to reflect any text the user has typed in.  Without this call the current element is not going to be updated to point to the tag the user just typed in.  Next the method does is confirm that the current element is either a comment or XML doc comment depending upon the IsDocumentation property.  If the current element is a comment (of either sort)  then the method confirms that the next element (after the entire comment element) is of the appropriate type as determined by the ForElement property.  If all the above conditions are met then the context is valid otherwise it isn’t.


The final step is to associate the provider with the plugin.  Since the provider is configurable we can add it multiple times to support the various contexts we want (documenting a property, documenting a method, etc).  For now we’ll just create a single context for documenting a property. 


  1. Ensure the code is built.
  2. Switch to the plugin designer.
  3. Open the Toolbox and drag and drop an instance of the custom context provider onto the designer.
  4. Configure the provider.
    1. ProviderName is what shows up in the template editor in the context tree.  Normally this is a path such as Editor\Code\Documentation\InDocumentationForProperty.
    2. Description is the helpful description of what it does.
    3. DisplayName doesn’t really seem to be used.  It would seem that it should represent what the user would see in the context list but that doesn’t appear to happen.  I just set it to the last entry in ProviderName.
    4. Set the ForElement to Property.
    5. Set IsDocumentation to true.
  5. Rebuild the updated plugin.

Debugging CodeRush Addins


Debugging CR addins in Visual Studio (VS) is trivial.  The project template already has VS set as the program to be debugged.  Since this is a VSIX project the project is already configured to build the addin locally, generate the VSIX file and deploy it to an experimental instance of VS.  The experimental VS instance prevents cross-play between the stable version and the version running the addin.  In my experience though it doesn’t actually install the VSIX so I have to do so manually.  You can confirm it is installed by using Extension Manager.  I personally have had little luck getting this approach to work.  In addition to the installation issue I also found that each time I made a code change I had to uninstall the VSIX otherwise the changes weren’t seen.


Because of the above issues I avoid debugging via the VSIX stuff.  Instead I switch back to the approach that worked pre-VSIX.  This approach has the plugin output to the standard directory (Documents\DevExpress\IDE Tools\Community\Plugins) that CR will auto-load from.  CR will then load the plugin the next time it starts.  The only downside to this approach is that CR will load the plugin even in the initial (debugging) copy of VS.  Since CR will lock the assemblies subsequent builds will fail.  To work around this tell CR to load the raw assembly via the DevExpress options.  You should also disable the auto-installation of the VSIX via the project property settings.



This is something you’ll want to turn back off when you’re done but for testing plugins it works well.  Now it is time to test the addin.


  1. Start the debugger.
  2. Create or open a project
  3. Set up a template that uses the new context provider.  In my case I defined a new /xout template that uses the context rules mentioned at the beginning of the article to put an exception out of range documentation tag in the code.
  4. Open a source file, type in the correct code to get the context to be valid and then type the template to verify the provider is working.
  5. If the provider does not work properly then switch back to the debugger and put a breakpoint into the code so it can be stepped through.

Resources


For more information on CodeRush and extending it refer to the the following links.


One Response to CodeRush – Writing a Context Provider Comments (RSS) Comments (RSS)