In previous articles, we covered the basics of dealing with comments in code analyzers. You'll recall that comments and other white space are referred to as trivia by the Roslyn compiler. XML Comments are also considered trivia, however, they are a special case of trivia called structured trivia.

If, for example, you wanted to check to see that all public methods have some sort of XML comment, you could simply write an analyzer like this:

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(CheckMethods, SyntaxKind.MethodDeclaration);
}

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;
    if (node.HasStructuredTrivia)
        return;

    if (node.Modifiers.Any(SyntaxKind.PublicKeyword))
        syntaxNodeAnalysisContext.ReportDiagnostic(Diagnostic.Create(Rule,node.GetLocation()));
}

All that we are checking in that code is that the method has no structured trivia and that it is a public method. At this point, the analyzer really isn't doing anything useful, other than ensuring that the method has a leading xml comment indicator (///). Next we could modify this to verify that it actually has a summary node in the XML. To do this, we need to get the structured XML and then find the XmlElementSytnax nodes that have a name of summary.

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;

    if (!node.Modifiers.Any(SyntaxKind.PublicKeyword))
        return;

    var xmlTrivia = node.GetLeadingTrivia()
        .Select(i => i.GetStructure())
        .OfType<DocumentationCommentTriviaSyntax>()
        .FirstOrDefault();

    var hasSummary = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Any(i => i.StartTag.Name.ToString().Equals("summary"));

    if (!hasSummary)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
           Diagnostic.Create(Rule, node.Identifier.GetLocation(), "Missing Summary"));
    }
}

If we find the summary is missing, we raise a diagnostic at the Identifier location, such that only the method name gets underlined.

We could take this a bit further by checking the parameters. To start we need to get the XmlElementSyntax nodes whose name are param. From those nodes, we will get all attributes that are of the type XmlNameAttributeSyntax.

var allParamNameAttributes = xmlTrivia.ChildNodes()
    .OfType<XmlElementSyntax>()
    .Where(i => i.StartTag.Name.ToString().Equals("param"))
    .SelectMany(i => i.StartTag.Attributes.OfType<XmlNameAttributeSyntax>());

Once this query is evaluated, allParamNameAttributes contains a list of all name attributes within the XML comments. Using these nodes, we can compare the identifiers to the names of the parameters in the method declaration and raise any diagnostics where a parameter is not documented.

foreach (var param in node.ParameterList.Parameters)
{
    var existsInXmlTrivia = allParamNameAttributes
                .Any(i=>i.Identifier.ToString().Equals(param.Identifier.Text)) ;// ()

    if (!existsInXmlTrivia)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
             Diagnostic.Create(Rule, param.GetLocation(), "Parameter Not Documented"));
    }
}

Putting it all together yields:

private void CheckMethods(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext)
{
    var node = syntaxNodeAnalysisContext.Node as MethodDeclarationSyntax;

    if (!node.Modifiers.Any(SyntaxKind.PublicKeyword))
        return;

    var xmlTrivia = node.GetLeadingTrivia()
        .Select(i => i.GetStructure())
        .OfType<DocumentationCommentTriviaSyntax>()
        .FirstOrDefault();

    var hasSummary = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Any(i => i.StartTag.Name.ToString().Equals("summary"));

    if (!hasSummary)
    {
        syntaxNodeAnalysisContext.ReportDiagnostic(
            Diagnostic.Create(Rule, node.Identifier.GetLocation(), "Missing Summary"));
    }

    var allParamNameAttributes = xmlTrivia.ChildNodes()
        .OfType<XmlElementSyntax>()
        .Where(i => i.StartTag.Name.ToString().Equals("param"))
        .SelectMany(i => i.StartTag.Attributes.OfType<XmlNameAttributeSyntax>());

    foreach (var param in node.ParameterList.Parameters)
    {
        var existsInXmlTrivia = allParamNameAttributes
                    .Any(i=>i.Identifier.ToString().Equals(param.Identifier.Text)) ;// ()

        if (!existsInXmlTrivia)
        {
            syntaxNodeAnalysisContext.ReportDiagnostic(
               Diagnostic.Create(Rule, param.GetLocation(), "Parameter Not Documented"));
        }
    }
}

As you can see, it is possible to get at the XML comments for any given node in the syntax tree, provided you know where to look. There is obviously a lot more work that could be put into this analyzer to ensure you are following all the rules. If you are looking for a complete analyzer, look no further than the StyleCopAnalyzers project on GitHub. They have a handful of documentation rules that cover the cases I outlined and many more. Note that some of the code in this article to get at the XML trivia was based on code that exists in the XmlCommentHelpers in the StyleCopyAnalyzers project.