如何使用Roslyn找到变量的先前用法?

时间:2015-08-18 15:23:55

标签: c# visual-studio-2015 code-analysis roslyn

我正在写一个Rosyln分析仪/分析仪。它会检查以确保在访问类型上的另一个(潜在危险的)方法之前调用方法。为了表明我的意思,这里有一些我想要分析和失败的错误代码:

private void myMethod()
{
    var myThing = new MyThing();
    myThing.Value = null;

    string value = myThing.GetValue(); // code blows up here as the internal value is null
}

这里的代码没问题,因为它调用的方法是否为null:

private void myMethod()
{
    var myThing = new MyThing();
    myThing.Value = null;

    if(!myThing.HasValue)
    {
        return ;
    }

    string value = myThing.GetValue(); 
}

因此,它应该检查所有对GetValue的来电是否都在调用HasValue之前。

我刚刚开始使用Roslyn,所以可能比我最初(失败)的尝试更优雅:

1 - 声明我想检查调用表达式

context.RegisterSyntaxNodeAction(analyseMemberAccessNode, SyntaxKind.InvocationExpression);

2 - 在我的方法中,我得到方法名称(GetValue()

var expr = (InvocationExpressionSyntax)context.Node;

var memberAccess = expr.Expression as MemberAccessExpressionSyntax;

if (memberAccess?.Name.ToString() != "GetValue")
    return;

3 - 然后我检查它是否是正确的'GetValue'

var memberSymbol = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol as IMethodSymbol;

if (!memberSymbol?.OverriddenMethod.ToString().StartsWith("MyNamespace.MyThing.GetValue") ?? true)
    return;

4 - 到此为止,一切都很好。所以我得到变量的名称

var e = memberAccess.Expression as IdentifierNameSyntax;

string variableName = e.Identifier.Text;

5 - 现在我被困了 - 我的理论是;获取包含方法,找到匹配variableName的单个变量声明,找到它的用法,并确保在HasValue之前调用GetValue

简而言之,使用Roslyn分析器(派生自DiagnosticAnalyzer),如何确保在HasValue之前调用GetValue

1 个答案:

答案 0 :(得分:2)

您可能最好注册整个方法声明,而不是注册每个Invocation。然后,您可以跟踪所有MemberAccessExpressionSyntax,并确保在HasValue之前调用GetValue的给定变量。为此,我会从MemberAccessExpressionSyntax节点获取MethodDeclaration个后代。

context.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;

        string variableName = e.Identifier.Text;

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

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