Extending FxCop Rules with fixing ability

June 28, 2011 14 comments

Whenever I see a tool telling a developer what to do to fix a problem and that problem is relatively easy to fix, I tend to label the creator of the tool as lazy. If you can find out a problem and know how to fix it, why don’t you offer the solution in place?

This is the case with static CodeAnalysis of Visual Studio. It issues FXCop rules to generate warnings/information in the form of "CAXXXX: Micorsoft.xxxx: the message”.

Wouldn’t it be nice to have the ability to right-click on the warning and tell it to fix it?

screenshot

Well, this is what this blog is about: extending FxCop rules with the ability to fix itself.

 

How it works

 

It’s actually very simple. We only have an ErrorItem object as input, retrieved from the ErrorList. An ErrorItem does not give you much more than textual information about the origin of the warning/error. No object linked to be used. Only the columns shown in the ErrorList are the properties of the ErrorItem. Luckely it shows the rule identity (CAXXXX). We can use this to look for the fix.

To gather the fixes we make use of an interface, IFixable.  The interface looks like this:

image

Members:

  • ErrorMessage: the VS Package will pass the description of the ErrorItem before calling the FixIt
  • IsBreaking: indicates when the fix is applied it will break the code
  • RuleIdentifier: here the implementor indicates the RuleId that will be fixed
  • FixIt(fileName, lineNr, Column): the actual fix. Notice nullable integers are used since the ErrorItem not always gives the needed information.

The VS Package has the responsibility of loading the assemblies and showing the command in the context menu. Some notifications (like when it is breaking) to the user are also added. It also provides a FileManager as a tool for loading and saving the file in the VS IDE.

The Package makes use of MEF to load the fixing assemblies. This means you’ll need to apply an export of the interface IFixable. The FileManager, mentioned above, can be imported using the IFileManager interface.

Example of an implementation:

using System.ComponentModel.Composition;
using Elbanique.FxCopRuleFixVSPackageLib;

namespace RecommendedRulesFixes.Design
{
    /// <summary>
    /// A delegate that handles a public or protected event does not have 
    /// the correct signature, return type, or parameter names.
    /// To fix a violation of this rule, correct the signature, 
    /// return type, or parameter names of the delegate. 
    /// </summary>
    [Export(typeof(IFixable))]
    public class CA1009 : IFixable
    {
        const string id = "CA1009";

        [Import(typeof(IFileManager))]
        private IFileManager _FileManager { get; set; }

        /// <summary>
        /// Gets the rule identifier.
        /// </summary>
        public string RuleIdentifier { get { return id; } }

        /// <summary>
        /// Gets a value indicating whether this instance is breaking.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance is breaking; otherwise, <c>false</c>.
        /// </value>
        public bool IsBreaking { get { return true; } }

        /// <summary>
        /// Gets or sets the error message.
        /// </summary>
        /// <value>
        /// The error message.
        /// </value>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// Fixes the rule.
        /// </summary>
        /// <param name="originalFile">The original file.</param>
        /// <param name="lineNr">The line nr.</param>
        /// <param name="column">The column.</param>
        /// <returns>
        /// True if success, otherwise false.
        /// </returns>
        public bool FixIt(string originalFile, int? lineNr, int? column)
        {
            if (_FileManager == null)
                return false;

            _FileManager.Open(originalFile);
            string contextLine = null;
            if (lineNr.HasValue)
            {
                contextLine = _FileManager.GetLine(lineNr.Value -1);
                string fixedLine = FixLine(contextLine);
                _FileManager.ReplaceLine(lineNr.Value - 1, fixedLine);
                _FileManager.Save();
                return true;
            }

            return false;
        }

        private string FixLine(string contextLine)
        {
            //should be of type:  
            //public delegate void AlarmEventHandler(object sender, EventArgs e);
            MethodParser mp = MethodParser.Create(contextLine);
            if (mp.Return != "void") 
                mp.Return = "void";

            if (mp.Arguments[0].Type != "object")
                mp.Arguments[0].Type = "object";
           
            //should check if the argument is of type EventArgument, 
            //no compiled code, thus no can do.

            return mp.ToString();
        }
    }
}

Before you can use the FxCopRuleFixVSPackageLib you have to install the VSPackage. You can find it here.

The location after installation is %USER%\AppData\Local\Microsoft\VisualStudio\10.0\Extensions\Elbanique\FxCopRuleFixVSPackage\1.0\

Now there is still the issue of register the fixing assembly in the MEF container. This can be done by adding a MefComponent to the extension.vsixmanifest file (also located in the installation directory).

For example, presume you created a RecommendedRulesFixes.dll stored on a local drive c:\FxCopExtensions\ this should become:

<?xml version="1.0" encoding="utf-8"?>
<Vsix xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2010">
  <Identifier Id="FxCopRuleFixVSPackage.Microsoft.87948990-c755-459d-b04d-4060ea76f5a4">
    <Name>FxCopRuleFixVSPackage</Name>
    <Author>Elbanique</Author>
    <Version>1.0</Version>
    <Description xml:space="preserve">This package is a tool that allows you to import (MEF) FxCop Rule repair assemblies. In the ErrorList, when selecting an CA-rule and an extension is found to fix it, the 'Fix It' item in the context menu will show up.</Description>
    <Locale>1033</Locale>
    <MoreInfoUrl>http://vsxexperience.net</MoreInfoUrl>
    <Icon>VS2010-FIXIT.png</Icon>
    <PreviewImage>screenshot.png</PreviewImage>
    <SupportedProducts>
      <VisualStudio Version="10.0">
        <Edition>Pro</Edition>
      </VisualStudio>
    </SupportedProducts>
    <SupportedFrameworkRuntimeEdition MinVersion="4.0" MaxVersion="4.0" />
  </Identifier>
  <References>
    <Reference Id="Microsoft.VisualStudio.MPF" MinVersion="10.0">
      <Name>Visual Studio MPF</Name>
    </Reference>
  </References>
  <Content>
    <VsPackage>FxCopRuleFixVSPackage.pkgdef</VsPackage>
    <MefComponent>FxCopRuleFixVSPackageLib.dll</MefComponent>
    <MefComponent>C:\FxCopExtensions\RecommendedRulesFixes.dll</MefComponent>
  </Content>
</Vsix>

 

— Any suggestions on improving the way of registering assemblies are welcome! —

NOTES:

  • You can imagine this is not the silver bullet because we have to work with a file as input. No CodeDOM or IL. Therefore, not all rules can be fixable. I’ve deliberately excluded the usage of EnvDTE or MPF when interfacing with fixing assemblies. If the future should prove the nenecessity, a supplementairy interface could be created passing the EnvDTE.
  • The package has an assembly (RecommendedRulesFixes.dll) included which – just for demo reasons – implements some CA rules (CA1009, CA1033, CA1305, CA2112, CA1801, CA2200). If there is enough response on this, I’ll consider to extend this assembly covering all CA rules.

Feature Builder Product Fiche

March 20, 2011 Leave a comment

I added a PDF document to the VSX Reference page. The pdf has 2 pages, one with a view for the user (or developer) and one for the creator (the Author).
I use it to give customers a quick view on feature builder to help them make a decission.
Free to use, ofcourse.

Feature Builder and Dynamic Visibility

March 10, 2011 2 comments

Recently I ran into the case where I had to make a context menu item dynamic visible. In this case the menu item must only be visible when the menu is activated on the context of a configuration file, thus when the file has the ‘.config’ extension.

With VSX this would be done by handling the BeforeQueryStatus event of the OleMenuCommand. Within Feature builder the separation between the behavior and execution is applied through the concept of LaunchPoint and Command. This allows you to write all execution logic within one library, while the Feature specific items are centralized around the package project. In my opinion, this is a very powerful concept making the package clean and specific, and the library reusable for other projects, especially when applying MEF.

So now the question: the LaunchPoint is located in the package project and holds the trigger for the BeforeQueryStatus (LaunchPoint inherits from OleMenuCommand), but I do not want to write this logic in the package project but in my library project.

If you take a closer look, you can find a QueryStatusStrategy property in the LaunchPoint class(*). It returns a IQueryStatusStrategy interface. This should ring a bell: interface..strategy… DI! Alright, it is setup to make use of MEF. So we can place our logic in the library and import it in the LaunchPoint.

(*) Also a OnQueryStatus method is available and could be used, but this would not follow the separation methodology.

Let’s apply this.

IQueryStatusStrategy

This interface only has one method to implement:

public interface IQueryStatusStrategy
{
    QueryStatus QueryStatus(IFeatureExtension feature);
}

where QueryStatus is a propertybag with 2 boolean properties, Enabled and Visible, and IFeatureExtension a reference to the extension calling this method

To get to the selected item we use the EnvDTE. To obtain it, import the SVsServiceProvider. Check on the extension and set the Visible property of the local created QueryStatus.

Wrapping it up results in this:

public class OnlyVisibleOnConfigQueryStrategy : IQueryStatusStrategy
{
    [Import]
    public SVsServiceProvider ServiceProvider { get; set; }

    public QueryStatus QueryStatus(IFeatureExtension feature)
    {
        QueryStatus queryStatus = new QueryStatus();
        queryStatus.Visible = false;
        queryStatus.Enabled = true;

        //
        //check to see if item is a config file (check on extension)
        //
        var dte = ServiceProvider.GetService<EnvDTE.DTE>();
        if (dte != null && dte.SelectedItems.Count > 0)
        {
            var firstItem = dte.SelectedItems.Item(1);
            if (firstItem != null &&
                firstItem.Name.EndsWith(".config"))
            {
                queryStatus.Visible = true;
            }
        }

        return queryStatus;
    }
}

 

Now we just have to export it to the MEF. The feature package already does the importing for the library project, so no extra effort needed here to make it visible in the container.

To export, place the export attribute on the class. I use a contractname to identify the strategy:

[Export("OnlyVisibleOnConfigQueryStrategy", typeof(IQueryStatusStrategy))]
public class OnlyVisibleOnConfigQueryStrategy : IQueryStatusStrategy

LaunchPoint

We cannot apply the import on the generated class, otherwise it would be overridden when regenerated. So create a partial class in the package project and implement the import of the QueryStatusStrategy on this partial class.

The full implementation of the LaunchPoint looks like this:

public partial class VsLaunchPointDynamic : VsLaunchPoint
{
    [Import("OnlyVisibleOnConfigQueryStrategy", typeof(IQueryStatusStrategy))]
    protected override IQueryStatusStrategy QueryStatusStrategy
    {
        get { return base.QueryStatusStrategy; }
        set { base.QueryStatusStrategy = value; }
    }
}

 
That’s all to it.

Working this way, you can build a strategy library with common scenario’s.

Maybe this can have a place in the Feature builder contrib.

Categories: Feature Builder Power Tool Tags: ,

Feature Builder Power Tool Review – Introduction

September 1, 2010 Leave a comment

Recently I installed the Feature Builder Power Tool from here. This VSIX file will install into the Visual Studio Extensions. You should be able to see it appear in your Installed Extensions of the Extension Manager (Tools > Extension Manager).

Important: currently it’s only available for the Visual Studio Ultimate edition to author the feature extension, though it can be deployed and used in the Professional edition.

Prerequisites: Visual Studio SDK

The Feature Builder Power Tool (FBPT) is intended as the successor of Guidance Automation, thus to make it easier to reuse code-based assets and practices by providing a predictable way of packaging and deploying them in Visual Studio. With the introduction of Visual Studio Visualization and Modeling (successor of DSL) and MEF there was a new set of tools within Visual Studio to achieve this goal. Besides the new tools Workflow is added, more later on this topic. As with the DSL tools, the package itself is build with it’s own package.

FBPT is build upon 3 core concepts: Map, Tools and Code.

  • Map: guidance for the developer and associated documentation
  • Tools: Visual Studio Automation
  • Code: artifacts

The package comes with a set of pre-configured project templates, which gives you a quick start. You can find them in the New Project dialog under the Extensibility node as shown in figure.

image

Let’s look which template contains what and what it is intended for.

  1. Info Pack Scenario: Guidance through documentation only; issues only the Map
  2. VSX Pack Scenario: Automation only; issues only the Tool
  3. Starter Pack Scenario: Documents and Source Code; issues the Map and Tools
  4. Starter Pack + Tools Scenario: Automation, Source Code and Documents; issues the Tools, Code and Map
  5. Blueprint Scenario: Automation, Source Code and Documents; issues the Tools, Code and an ”enhanced” Map (additional pre- and post-conditions on the guidance)
  6. Ultimate Blueprint Scenario: above + additional modeling features
  7. Modeling scenario: only the modeling features, issues an ”enhanced” Map

In my opinion, these project templates have no added value when developing a packages. On the other hand, I find them very useful for didactical usage, to explore the possibilities.

Beside the scenario focused project templates there are 2 “naked” project templates: Feature Extension for Premium And Ultimate and Feature Extension for Pro, Premium and Ultimate. Both have a simple one-step workflow and an empty project template. The later can contain tooling that leverages Architect Explorer and UML modeling.

When starting a new project one of the last 2 are recommended.

In future blogs I will first explain the 3 basic concepts and explain the interaction between them to establish a package. Automatically, we’ll hit one of the above project templates.

I should also mention, since the project templates are build with the FBPT itself, when opening a project from the list, it should already give you a good idea of the capabilities of FBPT.

Feature Builder Power Tool – release 1.0 coming very soon

Feature Builder is a Power Tool for Visual Studio 2010 promises that it wil help to easily create rich Visual Studio extensions. I should quickly package up sample code with custom menus, or create complete automated guidance experiences targeted toward a specific technology.
I’m very curious and wil shortly dive into it.

Keep track of http://visualstudiogallery.msdn.microsoft.com/en-us/396c5990-6356-41c0-aa20-af4c3e58c7ae to get the release.

The build process of an Integration Package (part I)

March 31, 2010 Leave a comment

This series of posts describe the inner workings of how a package is build with focus on the VSCT file. In this part we’ll ravel out the parts of the build process.

When building a package in Visual Studio, the project file is used as an input on which items to include in the build. When you open up a csproj file of a integration package project (unload the project and open for edit) you can find next items which are related to the package type project:

  • EmbeddedResource Include=”VSPackage.resx” with children MergeWithCTO and ManifestResourceName
  • None Include=”source.extension.vsixmanifest” with child SubType Designer
  • VSCTCompile Include”xxx.vsct” with children ResourceName and SubType Designer
  • A PropertyGroup with children TargetRegistryRoot, RegisterOutputPackage and RegisterWithCodebase
  • An Import tag with attribute Project=”…Microsoft.VsSDK.Targets”
    The VSPackage.resx is a resource for the package ;-) . You can find references to it in the InstalledProductRegistrationAttribute on the package class.

The source.extension.vsixmanifest is the designer for the VSIX manifest, it declares what type of extensions are provided by the content in the VSIX. For more info about VSIX files see Quan To’s post on VSIX.

The VSCT file is the source for the command table (see further). The <SubType>Designer</Subtype> on the vsct file tells VS to show a View Code/View Designer group in the context menu. In both cases it opens up a xml designer.

The PropertyGroup are options of the VSIX build process and can be found in the property page of the project in the VSIX tab.

All the previous items are used during the build process. This process is declared in the import tag pointing to a build targets file : “$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\VSSDK\Microsoft.VsSDK.targets”. This file can be found typically in the “C:\Program Files\MSBuild\Microsoft\VisualStudio\v10.0\VSSDK” folder. When you open this folder you can find the Microsoft.VsSDK.targets, Microsoft.VsSDK.Common.targets files and an assembly Microsoft.VsSDK.Build.Tasks. These are the key players of the build process.

If we take a look at the tasks, we could categorize them as in beneath image:

VSSDK Build steps

  1. Check On VsSDK installation: checks if VsSDK is installed
  2. Compile VSCT: compiles the VSCT file into a CTO file (command table)
  3. Deploy VSIX: prepares for the deployment of the package
  4. Merge CTO resource: check on resources (like MenuResource) to merge into the cto file
  5. Generate Code from LEX/YACC files: the language token and lexical classes used to enable syntax coloring (see Babel)
  6. Generate PkgDef file: generates the package definition file which represents registry values that are created when an application is installed on a computer and that are referenced by the Visual Studio Shell when it starts the application.
  7. Generate Zip file: generates the Zip file to be deployed

In next post we’ll take a closer look at the tasks related to the VSCT file.

Categories: VSX Tags:

Writing to the VS ErrorList

March 23, 2010 4 comments

The Shell provides a class to gain access to the ErrorList pane : ErrorListProvider. It implements the Add/Remove of ErrorList items in the ErrorList pane. To gain access to this provider it expects a ServiceProvider in the constructor. This provider can be the GlobalService of the Package class.

An ErrorList item is a class (called ErrorTask) where you can set all needed properties to navigate to the location in the document, categorize it and show the message.

So, it’s a quite simple and natural way of showing items in the ErrorList. I’ll show you the details now.

The simpliest approach is to create a helper class that implements the IServiceProvider interface; the implementation body of this interface (GetService(Type)) is a call to the Package.GetGlobalService(serviceType). Now we enabled the construction of the ErrorListProvider with this helper class.

public class ErrorListHelper : IServiceProvider
{
   public object GetService(Type serviceType)
    {
        return Package.GetGlobalService(serviceType);
    }
}

Now we can instantiate the ErrorListProvider: it is important here to give the provider a name and a new guid. Try to avoid making a new (= new guid and name) ErrorListProvider for each call. Consider making a singleton to access the helper class, if so make it scope the package only (for example by making the class generic).

public class ErrorListHelper : IServiceProvider
{
    public ErrorListProvider GetErrorListProvider()
    {
        ErrorListProvider provider = new ErrorListProvider(this);//this implementing IServiceProvider
        provider.ProviderName = {name scoping package};
        provider.ProviderGuid = {guid scoping package};
        return provider;
    }
}

Now that we have the ErrorListProvider in place, we have to make the helper class to add and remove items from the list: this includes, creating a ErrorTask object and handle the event to navigate to the context document’s position.

Lets start taking a look on how to create a ErrorTask object:

public void Write(
            TaskCategory category,
            TaskErrorCategory errorCategory,
            string context, //used as an indicator when removing
            string text,
            string document,
            int line,
            int column)
{
    ErrorTask task = new ErrorTask();
    task.Text = text;
    task.ErrorCategory = errorCategory;
    //The task list does +1 before showing this numbers
    task.Line = line - 1;
    task.Column = column - 1;
    task.Document = document;
    task.Category = category;

    if(! string.IsNullOrEmpty(document))
    {
        //attach to the navigate event
        task.Navigate += NavigateDocument;
    }   
    GetErrorListProvider().Tasks.Add(task);//add it to the errorlistprovider

}

Now that we have the ErrorTask in place, we can take a look at the handling of the Navigate event. This is a bit of code that I have centralized in another helper class to be reusable.

void NavigateDocument(object sender, EventArgs e)
{
    Task task = sender as Task;
    if (task == null)
    {
        throw new ArgumentException("sender");
    }
    //use the helper class to handle the navigation
    ShellContext.OpenDocumentAndNavigateTo(task.Document, task.Line, task.Column);
}

What it must cover:

  • get the VS service to open the document (IVsShellOpenDocument)
  • open the document via the OpenDocumentViaProject; this gives you a pointer to the frame (IVsWindowFrame) hosting the document
  • use the frame to get the VsTextBuffer
  • get the VS service that manages the textbuffer (VsTextManager) and use it to navigate to the position in the document

Here is the peace of code that does this:

    public static void OpenDocumentAndNavigateTo(
                                    string path, int line, int column)
    {
        IVsUIShellOpenDocument openDoc =
            Package.GetGlobalService(typeof(IVsUIShellOpenDocument))
                    as IVsUIShellOpenDocument;
        if (openDoc == null)
        {
            return ;
        }
        IVsWindowFrame frame;
        Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp;
        IVsUIHierarchy hier;
        uint itemid;
        Guid logicalView = VSConstants.LOGVIEWID_Code;
        if (ErrorHandler.Failed(
            openDoc.OpenDocumentViaProject(path, ref logicalView, out sp, out hier, out itemid, out frame))
            || frame == null)
        {
            return ;
        }  
        object docData;  
        frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocData, out docData);  

        // Get the VsTextBuffer  
        VsTextBuffer buffer = docData as VsTextBuffer;  
        if (buffer == null )  
        {  
            IVsTextBufferProvider bufferProvider = docData as IVsTextBufferProvider;  
            if (bufferProvider != null )  
            {  
                IVsTextLines lines;  
                ErrorHandler.ThrowOnFailure(bufferProvider.GetTextBuffer(out lines));  
                buffer = lines as VsTextBuffer;  
                Debug.Assert(buffer != null, "IVsTextLines does not implement IVsTextBuffer");  
                if (buffer == null)  
                {  
                   return ;  
                }  
            }  
        } 
        // Finally, perform the navigation.  
        IVsTextManager mgr = Package.GetGlobalService(typeof(VsTextManagerClass))
             as IVsTextManager;  
        if (mgr == null)  
        {  
            return;  
        }  
        mgr.NavigateToLineAndColumn(buffer, ref logicalView, line, column, line, column);  
    }

To remove the ErrorTask from the list, make use of the same Guid/Name to re-instantiate the ErrorListProvider.  The context argument in the write method can be used to remove all ErrorTasks for this context. The handling with the context is not covered here, because this post is focussed on the element to make this work.

When removing you should use the SuspendRefresh() before removing and ResumeRefresh() after the items are removed to avoid flickering.

 With all this parts paste together it should do the job, for me it did ;-)

Categories: VSX Tags: ,
Follow

Get every new post delivered to your Inbox.