Archive

Archive for June, 2011

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.
Follow

Get every new post delivered to your Inbox.