Recently there was a question on StackOverflow that piqued my interest. The question was basically asking how an analyzer could ensure that one method on an instance was called prior to calling another method on an instance.

An example of this would be if you wanted to ensure you called the HasValue method on a System.Nullable before you called the Value property. I provided an answer to the question, but let's dig a little deeper here.

Since we want to look at System.Nullable, we'll first need to get that type to check it against variable types when doing our analysis. To do this, we will register an action to run on compilation start, as I demonstrated in the Working with Types in Your Analyzer post. We'll also register a syntax node action to analyze all MethodDeclaration nodes.

context.RegisterCompilationStartAction((compilationStartContext) =>
{
    var nullableType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Nullable`1");
    compilationStartContext.RegisterSyntaxNodeAction((analysisContext) =>
    {
        // ...
    }, SyntaxKind.MethodDeclaration);
});

Next, we will capture all MemberAccessExpressionSyntax nodes from the method, which will give us all nodes where an object is accessed (i.e. method calls, property calls, field accesses, ...).

var invocations =
    analysisContext.Node.DescendantNodes().OfType<MemberAccessExpressionSyntax>();

We'll also create a HashSet<string> to keep track of all the HasValue calls for a given variable.

var hasValueCalls = new HashSet<string>();

Now we can iterate over the invocations to keep track of the HasValue and Value calls. To start, we check if the Expression on the invocation is anIdentifierNameSyntax`:

foreach (var invocation in invocations)
{
    var e = invocation.Expression as IdentifierNameSyntax;

    if (e == null)
        continue;

    //...
}

Next we will use the Semantic Model to get the type information from the expression. To do this we simply, call the GetTypeInfo method on the SemanticModel:

var typeInfo = analysisContext.SemanticModel.GetTypeInfo(e).Type as INamedTypeSymbol;

We now have the type info, so we will check that it is a System.Nullable type. Since all base nullable types (i.e. int?, bool?, ...) are constructed from the System.Nullable type, we can use the nullableType we captured earlier to verify the type.

if (typeInfo?.ConstructedFrom == null)
    continue;

if (!typeInfo.ConstructedFrom.Equals(nullableType))
    continue;

At this point, we know that the variable is a System.Nullable, so we can now check if the invocation is a HasValue or a Value call. If it is a HasValue call, we will add that variable to our HashSet, so we know that HasValue has been called on this variable.

string variableName = e.Identifier.Text;

if (invocation.Name.ToString() == "HasValue")
{
    hasValueCalls.Add(variableName);
}

Finally, we can check the Value calls and see if there was a previous HasValue call for the variable name. If not, we can raise a diagnostic.

if (invocation.Name.ToString() == "Value")
{
    if (!hasValueCalls.Contains(variableName))
    {
        analysisContext.ReportDiagnostic(Diagnostic.Create(Rule, e.GetLocation()));
    }
}

So now, if we try some code against this analyzer, we will see the following code raise a diagnostic:

int? x = null;
var n = x.Value;  

While the following code will not raise a diagnostic:

int? x = null;
if (x.HasValue)
{
    var n = x.Value;
}

In the end, the full diagnostic is:

public override void Initialize(AnalysisContext context)
{
    context.RegisterCompilationStartAction((compilationStartContext) =>
    {
        var nullableType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Nullable`1");
        compilationStartContext.RegisterSyntaxNodeAction((analysisContext) =>
        {
            var invocations =
                analysisContext.Node.DescendantNodes().OfType<MemberAccessExpressionSyntax>();
            var hasValueCalls = new HashSet<string>();
            foreach (var invocation in invocations)
            {
                var e = invocation.Expression as IdentifierNameSyntax;

                if (e == null)
                    continue;

                var typeInfo = analysisContext.SemanticModel.GetTypeInfo(e).Type as INamedTypeSymbol;

                if (typeInfo?.ConstructedFrom == null)
                    continue;

                if (!typeInfo.ConstructedFrom.Equals(nullableType))
                    continue;

                string variableName = e.Identifier.Text;

                if (invocation.Name.ToString() == "HasValue")
                {
                    hasValueCalls.Add(variableName);
                }

                if (invocation.Name.ToString() == "Value")
                {
                    if (!hasValueCalls.Contains(variableName))
                    {
                        analysisContext.ReportDiagnostic(Diagnostic.Create(Rule, e.GetLocation()));
                    }
                }
            }
        }, SyntaxKind.MethodDeclaration);
    });
}

Getting the order of method calls can be a bit tricky, but once you work out the logic and the syntax nodes that you need to process, the work to be done becomes clear. If you have a story of an analyzer you wrote that does something similar, share it in the comments below.