簡體   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