Roslyn - 获取对分配给 属性 的字符串的引用

Roslyn - obtain reference to a string assigned to property

在多个项目的 Visual Studio 解决方案中,我的代码片段类似于:

sqlCommand.CommandText = "Some SQL statement";

我能够通过以下方式获得对 CommandText 作为解决方案内部 Callee 的所有引用:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
// ...

var project = _solution.GetProject("my-project");
            

var compilation = await project.GetCompilationAsync();
var sqlCmdSymbol = compilation.GetTypeByMetadataName(typeof(SqlCommand).FullName);
var cmdTextSymbol = sqlCmdSymbol.GetMembers("CommandText").First();

IEnumerable<SymbolCallerInfo> allReferences = await SymbolFinder.FindCallersAsync(cmdTextSymbol, _solution);
var calledSymbolReferences = allReferences.Where(r => SymbolEqualityComparer.Default.Equals(r.CalledSymbol, cmdTextSymbol)).ToArray();

foreach (SymbolCallerInfo reference in calledSymbolReferences)
{
    // How to get from `SymbolCallerInfo` to a `LiteralExpressionSyntax` following it?
    // (eg .CommandText = "Some SQL statement";)

}

Microsoft Docs 中有一个代码示例如何捕获字符串文字:

// Use the syntax model to find the literal string:
LiteralExpressionSyntax helloWorldString = root.DescendantNodes()
    .OfType<LiteralExpressionSyntax>()
    .Single();

// Use the semantic model for type information:
TypeInfo literalInfo = model.GetTypeInfo(helloWorldString);

但我不确定如何从 SymbolCallerInfo 类型的引用到 属性 CommandText 到分配给它的字符串?

您需要寻找 AssignmentExpressionSyntaxCommandText 属性 语法作为其 Left 表达式。

您可以从 SymbolCallerInfo referenceLocations 属性 中找到该语法,但根据我的经验,反过来更容易。

您要查找分配给 属性 的文本,因此我会搜索 AssignmentExpressionSyntax

在分析器中,您将重写 Initialize 方法,如下所示:

public override void Initialize(AnalysisContext context)
{
    context.EnableConcurrentExecution();
    context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);

    context.RegisterSyntaxNodeAction(
        AnalyzeAssignmentExpression, SyntaxKind.SimpleAssignmentExpression);
}

请注意,我们在这里只寻找简单的作业;即使用 = 的赋值。我想您也可以考虑寻找 SyntaxKind.AddAssignmentExpression,即使用 += 进行赋值,但要确定赋值的确切内容并不容易,因此我将其排除在外。

所以现在我们需要实现AnalyzeAssignmentExpression方法。

private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)

我们做的第一件事是获取我们正在分析的AssignmentExpressionSyntax node

private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)
{
    var node = (AssignmentExpressionSyntax)context.Node;

该节点具有我们需要查看的 LeftRight 属性。让我们从 Left 属性 开始,找出它指的是什么符号。

    ISymbol left = context.SemanticModel.GetSymbolInfo(
        node.Left, context.CancellationToken).Symbol;

我们需要查明这个 Symbol 是否确实是我们的 CommandText 属性.

这里有一个小的辅助方法可以做到这一点:

private static bool IsCommandText(ISymbol symbol)
    => symbol is IPropertySymbol
    {
        Name: "CommandText",
        ContainingType:
        {
            Name: "SqlCommand",
            ContainingNamespace:
            {
                Name: "SqlClient",
                ContainingNamespace:
                {
                    Name: "Data",
                    ContainingNamespace:
                    {
                        // Allow both Microsoft.Data.SqlClient and System.Data.SqlClient
                        ContainingNamespace:
                        {
                            IsGlobalNamespace: true
                        }
                    }
                }
            }
        }
    };

(如果您知道您正在寻找的确切类型,您也可以将它的 ContainingType 与您使用 Compilation.GetTypeByMetadataName() 查找过的符号进行比较。我没有,所以我使用这个图案。)

使用该辅助方法,我们可以测试我们的 left 交易品种。如果不是我们的CommandText属性,我们可以停止分析这个node.

    if (!IsCommandText(left))
    {
        return;
    }

现在让我们看一下 Right 属性,正在分配的语法。您说您正在寻找 LiteralExpressionSyntax,但我建议您查看计算结果为常量字符串的任何语法。它不一定是文字。它可以是常量的名称,例如,两个文字的串联,或许多其他类型的表达式。但我认为你真正关心的是它的价值。那么让我们看看 Right 属性 是否有一个常量字符串值。 (我假设你对这里的值 null 不感兴趣。)如果它有一个常量字符串值,我们就找到了我们要找的东西。

    var right = context.SemanticModel.GetConstantValue(node.Right, context.CancellationToken);

    if (right.HasValue && right.Value is string value)
    {
        // value is a constant being assigned to SqlCommand.CommandText
        // your further analysis goes here
    }
}

整个方法:

private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)
{
    var node = (AssignmentExpressionSyntax)context.Node;

    ISymbol left = context.SemanticModel.GetSymbolInfo(
        node.Left, context.CancellationToken).Symbol;

    if (!IsCommandText(left))
    {
        return;
    }

    var right = context.SemanticModel.GetConstantValue(node.Right, context.CancellationToken);

    if (right.HasValue && right.Value is string value)
    {
        // value is a constant being assinged to SqlCommand.CommandText
        // your further analysis goes here
    }
}

如果您不是在分析器中执行此操作,您可以使用 root.DescendantNodes().OfType<AssignmentExpressionSyntax>() 找到 AssignmentExpressionSyntax 节点,根据您的问题,我认为您已经知道如何获得 [=45] =] 给他们。