Example: ArgumentException Parameter Names

Various constructors to ArgumentException have a paramName parameter. Typically, a String naming one of the calling method's parameters should be passed as this argument. As a special case, when the ArgumentException is constructed in a property, the paramName should be the property's name.

The implementation makes a call to VisitStatements, which recursively examines all of the expressions contained in the member, to find invocations of the constructor of ArgumentException or the constructor of a class that inherits from ArgumentException.

Rule XML:

<Rule TypeName="ArgumentExceptionParameterNaming" Category="Tutorial" CheckId="TT1040">
  <Name>ArgumentException constructor's paramName parameter should be a parameter name</Name>
  <Description>
    When an ArgumentException is constructed by a constructor that takes paramName,
    that paramName should be the name of a calling method's parameter, or the name of the
    property if called in a property setter.
  </Description>
  <Url></Url>
  <Resolution Name="Method">Pass the correct parameter name instead of '{0}'.</Resolution>
  <Resolution Name="Property">Pass '{0}' as the paramName argument.</Resolution>
  <MessageLevel Certainty="95">Warning</MessageLevel>
  <Email></Email>
  <FixCategories>NonBreaking</FixCategories>
  <Owner></Owner>
</Rule>

Rule Implementation:

using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.FxCop.Sdk;

namespace TutorialRules
{
  public class ArgumentExceptionParameterNaming : BaseIntrospectionRule
  {
    private Member m_CurrentMember;
    private TypeNode m_ArgumentException;

    public ArgumentExceptionParameterNaming() :
      base("ArgumentExceptionParameterNaming", "TutorialRules.TutorialRules",
      typeof(ArgumentExceptionParameterNaming).Assembly)
    {
    }

    public override void BeforeAnalysis()
    {
      m_ArgumentException = FrameworkAssemblies.Mscorlib.GetType(
        Identifier.For("System"), Identifier.For("ArgumentException"));
    }

    public override ProblemCollection Check(Member member)
    {
      if (member is Method)
      {
        m_CurrentMember = member;
        VisitStatements(((Method)member).Body.Statements);
      }
      else if (member is PropertyNode)
      {
        PropertyNode propNode = (PropertyNode)member;
        if (propNode.Setter != null)
        {
          m_CurrentMember = member;
          VisitStatements(propNode.Setter.Body.Statements);
        }
      }

      return this.Problems;
    }

    public override void VisitExpression(Expression expression)
    {
      Construct cnstruct;
      InstanceInitializer instInit;
      int i = 0;

      cnstruct = expression as Construct;
      if (cnstruct == null)
        return;

      if (!cnstruct.Type.IsAssignableTo(m_ArgumentException))
        return;

      instInit = (InstanceInitializer)((MemberBinding)cnstruct.Constructor).BoundMember;

      foreach (Expression operand in cnstruct.Operands)
      {
        if (instInit.Parameters[i].Name.Name == "paramName")
        {
          Literal lit;
          String litString;

          lit = operand as Literal;
          if (lit == null)
            continue;

          litString = lit.Value as String;
          if (litString == null)
            continue;

          if (m_CurrentMember is Method)
          {
            bool found = false;

            foreach (Parameter param in ((Method)m_CurrentMember).Parameters)
            {
              if (param.Name.Name == litString)
              {
                found = true;
                break;
              }
            }
            if (!found)
              this.Problems.Add(new Problem(this.GetNamedResolution("Method", litString)));
          }
          else // m_CurrentMember is PropertyNode
          {
            if (m_CurrentMember.Name.Name != litString)
              this.Problems.Add(new Problem(this.GetNamedResolution("Property", litString)));
          }
        }

        i += 1;
      }
    }
  }
}