![]() | Note |
|---|---|
|
This section is optional. It discusses a useful, advanced application of custom FxCop rules to check XML documentation comments. If this is your first time reading this document, I strongly recommend skipping to the examples. |
With some work, FxCop custom rules can be developed to check the XML documentation files generated by .NET compilers. For example, by checking assemblies against XML documentation files, you can verify that:
Every member has been documented.
All method parameters have been documented.
Method return values have been documented.
Namespaces have been documented.
FxCop does not provide any
built-in APIs to access the XML documentation file that is output by the
compiler alongside the assembly. Fortunately, being XML, that file is
fairly simple to parse using XmlDocument and other
standard .NET XML APIs. The most significant hurdle lies in turning the
names members (classes, methods, properties, and so on) into member "ID
strings". Such strings are similar to the strings returned by the
Member.FullName property. However,
FxCop does not provide the right conversion
function to the format used by documentation comments, and its
implementation is relatively tricky. The syntax of ID strings is described
by Microsoft under "Processing the XML File (C#
Programming Guide)" at http://msdn2.microsoft.com/en-us/library/fsbx0t7x.aspx.
Unfortunately, this official documentation does not completely describe
the syntax required to support generic types, and it has some ambiguities
and inconsistencies.
In this chapter, we present code that can generate a member ID
string from a (hopefully arbitrary) Member node. I
say hopefully due to the sheer complexity of this task. It is possible
that some esoteric cases are not handled correctly by the code given here.
Please report any such cases that you find.
This implementation differs from or expands on the Microsoft documentation mentioned above in at least the following ways:
Parameters of ELEMENT_TYPE_PINNED types
are not supported. I cannot figure out any way to generate such
parameters in compilers. I think pinning applies only to local
variables, not parameters. Thus, it looks like it is not applicable
to XML documentation.
Parameters of ELEMENT_TYPE_GENERICARRAY
types are not supported. This term is absent from the
ECMA CLI specification; I have no idea what the
documentation is referring to.
When a function pointer does have any parameters, the
parameters are represented as (System.Void).
Although the documentation states that the parentheses and
parameters should be omitted in this case, this implementation is
consistent with how the C++ compiler actually behaves.
Template arguments are rendered within curly braces. This facet is not explained in the documentation.
The names of methods with template parameters are suffixed with two backticks followed by the number of template parameters. The template parameters themselves are referenced with a double backtick notation. These facets are not explained in the documentation.
The lower bounds of ELEMENT_TYPE_ARRAY
arrays are always specified (often as 0). This
seems consistent with the C# compiler.
The source code is given below. We will use this source code in a later example that demonstrates how to check that all externally visible members are documented.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Microsoft.FxCop.Sdk;
namespace TutorialRules
{
sealed class DocComment
{
public static string GetMemberID(Member member)
{
char ch;
TypeNode declaringType = member.DeclaringType;
List<TypeNode> parentTypes = new List<TypeNode>();
List<TypeNode> typeTemplateParameters = new List<TypeNode>();
List<TypeNode> memberTemplateParameters = new List<TypeNode>();
StringBuilder sb = new StringBuilder();
if (member == null)
throw new ArgumentNullException("member");
// Determine prefix character.
switch (member.NodeType)
{
case NodeType.Class:
case NodeType.Interface:
case NodeType.Struct:
case NodeType.EnumNode:
case NodeType.DelegateNode:
ch = 'T';
break;
case NodeType.Field:
ch = 'F';
break;
case NodeType.Property:
ch = 'P';
break;
case NodeType.Method:
case NodeType.InstanceInitializer:
case NodeType.StaticInitializer:
ch = 'M';
break;
case NodeType.Event:
ch = 'E';
break;
default:
throw new ArgumentException("Unsupported NodeType.", "member");
}
// Determine all parent types of this potentially nested type.
for (TypeNode current = declaringType; current != null; current = current.DeclaringType)
{
parentTypes.Add(current);
}
parentTypes.Reverse();
// Collect all template parameters for the types.
foreach (TypeNode type in parentTypes)
{
if (type.TemplateParameters != null)
{
typeTemplateParameters.AddRange(type.TemplateParameters);
}
}
// Collect all template parameters for the method.
switch (member.NodeType)
{
case NodeType.Method:
case NodeType.InstanceInitializer:
case NodeType.StaticInitializer:
Method method = (Method)member;
if (method.TemplateParameters != null)
{
memberTemplateParameters.AddRange(method.TemplateParameters);
}
break;
}
// Output full method name.
sb.Append(ch);
sb.Append(':');
if (declaringType == null)
{
TypeNode type = member as TypeNode;
if (type != null)
{
if (type.Namespace.Name.Length != 0)
{
sb.Append(type.Namespace.Name);
sb.Append('.');
}
}
}
else
{
sb.Append(declaringType.FullName.Replace('+', '.'));
sb.Append('.');
}
sb.Append(member.Name.Name.Replace('.', '#'));
// Output number of template parameters.
if (memberTemplateParameters.Count != 0)
{
// Undocumented: based on output from MS compilers.
sb.AppendFormat(CultureInfo.InvariantCulture, "``{0}", memberTemplateParameters.Count);
}
// Output parameters.
ParameterCollection parameters;
switch (member.NodeType)
{
case NodeType.Property:
parameters = ((PropertyNode)member).Parameters;
break;
case NodeType.Method:
case NodeType.InstanceInitializer:
case NodeType.StaticInitializer:
parameters = ((Method)member).Parameters;
break;
default:
parameters = null;
break;
}
if (parameters != null && parameters.Count != 0)
{
bool comma = false;
sb.Append('(');
foreach (Parameter parameter in parameters)
{
if (comma)
{
sb.Append(',');
}
sb.Append(GetStringForTypeNode(parameter.Type,
typeTemplateParameters, memberTemplateParameters));
comma = true;
}
sb.Append(')');
}
// Output return type (for conversion operators).
if (member.NodeType == NodeType.Method && member.IsSpecialName &&
(member.Name.Name == "op_Explicit" || member.Name.Name == "op_Implicit"))
{
Method convOperator = (Method)member;
sb.Append('~');
sb.Append(GetStringForTypeNode(convOperator.ReturnType,
typeTemplateParameters, memberTemplateParameters));
}
return sb.ToString();
}
private static string GetStringForTypeNode(TypeNode type,
List<TypeNode> typeTemplateParameters, List<TypeNode> memberTemplateParameters)
{
StringBuilder sb = new StringBuilder();
switch (type.NodeType)
{
/* Ordinary types */
case NodeType.Class:
case NodeType.Interface:
case NodeType.Struct:
case NodeType.EnumNode:
case NodeType.DelegateNode:
if (type.DeclaringType == null)
{
if (type.Namespace.Name.Length != 0)
{
sb.Append(type.Namespace.Name);
sb.Append('.');
}
}
else
{
sb.Append(GetStringForTypeNode(type.DeclaringType,
typeTemplateParameters, memberTemplateParameters));
sb.Append('.');
}
if (type.IsGeneric)
{
String templateName = type.Template.Name.Name.Replace('+', '.');
int pos = templateName.LastIndexOf('`');
if (pos != -1)
{
sb.Append(templateName.Substring(0, pos));
}
else
{
sb.Append(templateName);
}
}
else
{
sb.Append(type.Name.Name.Replace('+', '.'));
}
break;
/* Simple pointer / reference types */
case NodeType.Reference:
sb.Append(GetStringForTypeNode(((Reference)type).ElementType,
typeTemplateParameters, memberTemplateParameters));
sb.Append('@');
break;
case NodeType.Pointer:
sb.Append(GetStringForTypeNode(((Pointer)type).ElementType,
typeTemplateParameters, memberTemplateParameters));
sb.Append('*');
break;
/* Generic parameters */
case NodeType.ClassParameter:
case NodeType.TypeParameter:
int index;
if ((index = typeTemplateParameters.IndexOf(type)) != -1)
{
sb.AppendFormat(CultureInfo.InvariantCulture, "`{0}", index);
}
else if ((index = memberTemplateParameters.IndexOf(type)) != -1)
{
// Undocumented: based on output from MS compilers.
sb.AppendFormat(CultureInfo.InvariantCulture, "``{0}", index);
}
else
{
throw new InvalidOperationException("Unable to resolve TypeParameter to a type argument.");
}
break;
/* Arrays */
case NodeType.ArrayType:
ArrayType array = ((ArrayType)type);
sb.Append(GetStringForTypeNode(array.ElementType,
typeTemplateParameters, memberTemplateParameters));
if (array.IsSzArray())
{
sb.Append("[]");
}
else
{
// This case handles true multidimensional arrays.
// For example, in C#: string[,] myArray
sb.Append('[');
for (int i = 0; i < array.Rank; i++)
{
if (i != 0)
{
sb.Append(',');
}
// The following appears to be consistent with MS C# compiler output.
sb.AppendFormat(CultureInfo.InvariantCulture, "{0}:", array.GetLowerBound(i));
if (array.GetSize(i) != 0)
{
sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", array.GetSize(i));
}
}
sb.Append(']');
}
break;
/* Strange types (typically from C++/CLI) */
case NodeType.FunctionPointer:
FunctionPointer funcPointer = (FunctionPointer)type;
sb.Append("=FUNC:");
sb.Append(GetStringForTypeNode(funcPointer.ReturnType,
typeTemplateParameters, memberTemplateParameters));
if (funcPointer.ParameterTypes.Count != 0)
{
bool comma = false;
sb.Append('(');
foreach (TypeNode parameterType in funcPointer.ParameterTypes)
{
if (comma)
{
sb.Append(',');
}
sb.Append(GetStringForTypeNode(parameterType,
typeTemplateParameters, memberTemplateParameters));
comma = true;
}
sb.Append(')');
}
else
{
// Inconsistent with documentation: based on MS C++ compiler output.
sb.Append("(System.Void)");
}
break;
case NodeType.RequiredModifier:
RequiredModifier reqModifier = (RequiredModifier)type;
sb.Append(GetStringForTypeNode(reqModifier.ModifiedType,
typeTemplateParameters, memberTemplateParameters));
sb.Append("|");
sb.Append(GetStringForTypeNode(reqModifier.Modifier,
typeTemplateParameters, memberTemplateParameters));
break;
case NodeType.OptionalModifier:
OptionalModifier optModifier = (OptionalModifier)type;
sb.Append(GetStringForTypeNode(optModifier.ModifiedType,
typeTemplateParameters, memberTemplateParameters));
sb.Append("!");
sb.Append(GetStringForTypeNode(optModifier.Modifier,
typeTemplateParameters, memberTemplateParameters));
break;
default:
throw new ArgumentException("Unsupported NodeType.", "type");
}
if (type.IsGeneric && type.TemplateArguments.Count != 0)
{
// Undocumented: based on output from MS compilers.
sb.Append('{');
bool comma = false;
foreach (TypeNode templateArgumentType in type.TemplateArguments)
{
if (comma)
{
sb.Append(',');
}
sb.Append(GetStringForTypeNode(templateArgumentType,
typeTemplateParameters, memberTemplateParameters));
comma = true;
}
sb.Append('}');
}
return sb.ToString();
}
// Prevent instantiation of this class; all members are static.
private DocComment()
{
}
}
}