繁体   English   中英

如何使用 Roslyn 查找调用方法的参数是变量(通过“var/string”)还是内联字符串

[英]How to find if a parameter to an invoked method is a variable (via "var/string") or an in-line string using Roslyn

我目前正在尝试查找 .ExecuteSqlCommand 的调用并检查传递给 sql 参数的第一个值。

这是我在我们的代码库中发现的差异示例。

ExecuteSqlCommand("[sql statement here]");

对比

var sql = "sql statement";
ExecuteSqlCommand(sql);

到目前为止,我有这个:

var invocations = root.DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .Select(ie => ModelExtensions.GetSymbolInfo(model, ie).Symbol)
    .Where(symbol => symbol != null && symbol.Name == "ExecuteSqlCommand");

foreach (var invocation in invocations)
{
    var method = (IMethodSymbol)invocation;
    foreach (var param in method.Parameters)
    {
        //I can't quite seem to get information from IParameterSymbol about whether the param is a string literal, or a reference to a string via a variable.
    }
}

如果 param 不是字符串,而是 var,那么我需要获取 var 的值(与它在运行时定义的一样多)。

我不太确定这是 SemanticModel 还是 SyntaxTree 的工作,但我的猜测是 SemanticModel 应该有更丰富的信息,我需要让我发现我在寻找什么。

我的总体目标是询问传递给 ExecuteSqlCommand 方法的 sql。

谢谢!

SemanticModel.GetConstantValue是我们用于处理这种情况的 API。

它可以接受语法节点表达式 您仍然需要跟踪变量的状态回到它们的声明位置,并确定它们是否被赋予了一个常量表达式。

我会使用SemanticModel.GetSymbolInfo .Symbol?。 DeclaringSyntaxReferences .First() 找到变量的声明位置,然后检查它是否是一个常量表达式。

语法 API 可用于提取 sql 语句值,但取决于变量声明(即var sql = "sql statement"; )是否作为提交到语法树的代码的一部分包含在内。

例如,如果它与调用ExcuteSqlCommand()方法实现相同,那么您可以首先获取传递给它的变量名称(即sql ),并使用它在同一方法中查找匹配的变量声明语句。 最后,可以从中提取 sql 语句值(即"sql statement" )。

以下代码首先检查 sql 值是否作为字符串文字传递,否则查找变量声明。 假设它都在同一个方法中:

        // whatever c# *method* code contains the sql. 
        // otherwise the root of the tree would need to be changed to filter to a specific single `MethodDeclarationSyntax`.
        string submittedCode = "public void SomeMethodContainingSql(){ ...//rest of code...";

        var tree = CSharpSyntaxTree.ParseText(submittedCode);
        var root = (CompilationUnitSyntax) tree.GetRoot();

        var arguments = root
            .DescendantNodes()
            .OfType<InvocationExpressionSyntax>()
            .First(node => node.DescendantNodes().OfType<IdentifierNameSyntax>()
                               .First()
                               .Identifier.Text == "ExecuteSqlCommand")
            .ArgumentList.DescendantNodes().ToList();

        string sqlStatementValue = "";

        var literalExpression = arguments.OfType<LiteralExpressionSyntax>().FirstOrDefault();

        if (literalExpression != null)
        {
            sqlStatementValue = literalExpression.GetText().ToString();
        }
        else
        {
            var variableName = arguments
                .First()
                .ToFullString();

            var variableDeclaration = root
                .DescendantNodes()
                .OfType<VariableDeclarationSyntax>()
                .Single(node => node.DescendantNodes().OfType<VariableDeclaratorSyntax>()
                                    .First()
                                    .Identifier.Text == variableName);

            sqlStatementValue = variableDeclaration.DescendantNodes()
                .OfType<LiteralExpressionSyntax>()
                .First()
                .DescendantTokens()
                .First()
                .Text;
        }

否则,可能需要在提交代码的其他部分(例如类字段、属性、其他方法等)中查找变量声明,这有点麻烦。

不幸的是,在运行时定义值的常见情况下,Roslyn 无法提供变量的值,因为 Roslyn 实际上不知道可能从程序外部传递的所有可能值,它不计算它们等等。 但是,如果您可以将所需的情况限制为在字符串中声明和初始化的内联字符串或变量,Roslyn 可以帮助您:

  • 您需要保留InvocationExpressionSyntax (或直接保留它们的第一个参数)
var invocations = root.DescendantNodes()
    .OfType<InvocationExpressionSyntax>()
    .Select(ie => (ModelExtensions.GetSymbolInfo(model, ie).Symbol, ie))
    // Would be better to compare not method's name but it FQN
    .Where((symbol, node)) => symbol != null && symbol.Name == "ExecuteSqlCommand" && 
            symbol.Parameters.Length == 1 && 
            symbol.Parameters[0].Type.SpecialType == SpecialType.System_String && 
            node.ArgumentList.Arguments.Count == 1)
    .Select((symbol, node) => node);
  • 您需要检查的不是方法参数,而是传递给它的方法参数
foreach (var invocation in invocations)
{
    var argument = invocation .ArgumentList.Arguments[0];
    if (argument is LiteralExpressionSyntax literal && literal.IsKind(SyntaxKind.StringLiteralExpression))
    {
        // You find invocation of kind `ExecuteSqlCommand("sql")`
    }
    else
    {
        var argSymbol = ModelExtensions.GetSymbolInfo(model, argument).Symbol;
        if (!(argSymbol is null) && (argSymbol.Kind == SymbolKind.Field || argSymbol.Kind == SymbolKind.Property || argSymbol.Kind == SymbolKind.Local))
        {
            if (argSymbol.DeclaringSyntaxReferences.Length == 1)
            {
                var declarationNode = argSymbol.DeclaringSyntaxReferences[0].GetSyntax();

                // I'm actually don't remember what exactlly `GetSyntax` returns for fields or locals: 
                // VariableDeclaratorSyntax or one of it parent LocalDeclarationStatementSyntax and FieldDeclarationSyntax, but if it returns declarations you also can 
                // get from them VariableDeclaratorSyntax that you need, it's just be a more deep pattern matching

                if (declarationNode is VariableDeclaratorSyntax declaratorSyntax && 
                    declaratorSyntax.EqualsValueClauseSyntax?.Value is LiteralExpressionSyntax literal2 && literal2.IsKind(SyntaxKind.StringLiteralExpression) )
                {
                    // You find invocation of kind `ExecuteSqlCommand(variable)` where variable is local variable or field 
                }
                else if (declarationNode is PropertyDeclarationSyntax property)
                {
                    // You can do the same things for properties initializer or expression body that you was do for fields and locals to check your case,
                    // but it doesn't work for property with get/set accessors in a common cases
                }
            }
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM