2014-03-09

Listing SuppressMessage Justifications

If you perform Code Analysis on a project, it will complain about many things. Most are legitimate. A few are not. When they are not, you can make the warning go away using the SuppressMessage attribute. This attribute decorates the method, property, or whatever, and causes the Code Analysis tool to ignore that warning in that scope. For example:

[SuppressMessage(
    "Microsoft.Design",
    "CA1031:DoNotCatchGeneralExceptionTypes",
    Justification = "Want to catch all exceptions here.")]

If you use StyleCop on the project, it will require the SuppressMessage to have the Justification property filled in. If you don't, then StyleCop will give a warning about a missing justification, which is a warning about the code that you used to make a different warning go away. So I fill in all my Justification properties.

I wanted to look at the justifications that I had used across an entire code base to make them consistent, and see if there were any that weren't really needed. As code gets refactored, the SuppressMessage attributes may no longer apply. The compiler, by the way, does not remove the SuppressMessage attributes; they get placed into the executable code.

I created a little tool that examines a directory and performs reflection on all the .exe and .dll files in it. It extracts all of the SuppressMessage attributes and writes their details to the console output. It's actually a nice little example of using reflection.

Below is the code for the Program.cs file of a console application. This is a quick implementation, for my own purposes, not a commercial product, so probably could be better in many ways. For example, it only searches for dlls with a lower case extension, because I never have upper case ones. In a commercial project, I'd test for either. I could fix that in less time than it took to write this sentence (using .ToUpper()), but it doesn't solve any problems for my environment. Making code commercial grade as opposed to a tool takes time.

The code takes a directory path on the command line and documents the justifications for all the .NET assemblies in that path.

//--------------------------------------------------------------------------------------------------
// <copyright file="Program.cs" company="Xoc Software">
// Copyright © 2014 Xoc Software
// </copyright>
// <summary>Implements the program class</summary>
//--------------------------------------------------------------------------------------------------
namespace Xoc.Justification
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.IO;
    using System.Reflection;

    /// <summary>The Justification Program.</summary>
    public static class Program
    {
        /// <summary>Main entry-point for this application.</summary>
        /// <param name="args">Array of command-line argument strings. args[0] must be path to examine.</param>
        public static void Main(string[] args)
        {
            if (args != null && args.Length > 0 && !string.IsNullOrEmpty(args[0]))
            {
                DirectoryInfo directoryInfo = new DirectoryInfo(args[0]);
                if (directoryInfo != null)
                {
                    Console.WriteLine("Finding Justifications in {0}", directoryInfo.FullName);
                    try
                    {
                        foreach (FileInfo file in directoryInfo.GetFiles())
                        {
                            switch (file.Name.ToUpper(CultureInfo.InvariantCulture))
                            {
                                // Ignore NLog. Add other DLLs to ignore here.
                                case "NLOG.DLL":
                                    break;
                                default:
                                    ProcessAssembly(file);

                                    break;
                            }
                        }
                    }
                    catch (IOException)
                    {
                        Console.WriteLine("Directory is invalid");
                    }
                }
            }
            else
            {
                Console.WriteLine("Syntax:\nXoc.Justification.exe <directory-path>");
            }
        }

        /// <summary>Process the assembly described by file.</summary>
        /// <param name="fileInfo">The FileInfo assembly for the assembly to document.</param>
        private static void ProcessAssembly(FileInfo fileInfo)
        {
            if (fileInfo.Extension == ".dll" || fileInfo.Extension == ".exe")
            {
                Assembly assembly = Assembly.LoadFrom(fileInfo.FullName);
                string assemblyName = assembly.GetName().Name;
                try
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        string typeName = type.Name;
                        BindingFlags flags =
                            BindingFlags.Static
                            | BindingFlags.Instance
                            | BindingFlags.Public
                            | BindingFlags.NonPublic;

                        var attributes = type.GetCustomAttributes<SuppressMessageAttribute>();
                        if (attributes != null)
                        {
                            foreach (SuppressMessageAttribute attribute in attributes)
                            {
                                if (attribute != null)
                                {
                                    AddToList(assemblyName, typeName, "type", attribute);
                                }
                            }
                        }

                        foreach (MemberInfo memberInfo in type.GetMembers(flags))
                        {
                            if (memberInfo != null)
                            {
                                EnumerateTypes(assemblyName, typeName, memberInfo);
                            }
                        }
                    }
                }
                catch (ReflectionTypeLoadException)
                {
                    Console.WriteLine("{0} not a .NET assembly.", assemblyName);
                }
            }
        }

        /// <summary>Adds to list.</summary>
        /// <param name="assembly">The assembly to document.</param>
        /// <param name="type">The type to document.</param>
        /// <param name="member">The member to document.</param>
        /// <param name="attribute">The SuppressMessage attribute.</param>
        private static void AddToList(
            string assembly,
            string type,
            string member,
            SuppressMessageAttribute attribute)
        {
            Contract.Requires<ArgumentNullException>(attribute != null);

            Console.WriteLine(
                "{0} | {1} | {2} | {3} | {4}",
                assembly,
                type,
                member,
                attribute.CheckId,
                attribute.Justification);
        }

        /// <summary>Enumerate types.</summary>
        /// <param name="assembly">The assembly to document.</param>
        /// <param name="type">The type to document.</param>
        /// <param name="memberInfo">Information describing the member.</param>
        private static void EnumerateTypes(string assembly, string type, MemberInfo memberInfo)
        {
            Contract.Requires<ArgumentNullException>(memberInfo != null);

            var attributes = memberInfo.GetCustomAttributes<SuppressMessageAttribute>();
            if (attributes != null)
            {
                foreach (SuppressMessageAttribute attribute in attributes)
                {
                    if (attribute != null)
                    {
                        AddToList(assembly, type, memberInfo.Name, attribute);
                    }
                }
            }
        }
    }
}

You can then add this as an external tool in Visual Studio tools menu. Set the argument to be  $(BinDir).

If you'd really like the complete project, let me know in the comments, and I'll do the extra work to publish it.

No comments :

Post a Comment

Note: Only a member of this blog may post a comment.