Throughout Visual Studio Solution with multiple projects, I have code snippets similar to:
sqlCommand.CommandText = "Some SQL statement";
I am able to obtain all references to CommandText
being a Callee
inside of a solution via:
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";)
}
There is a code example from Microsoft Docs how to capture string literal:
// 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);
But I'm not sure how to get from the reference of type SymbolCallerInfo
to a property CommandText
to the string that is assigned to it?
You need to look for a AssignmentExpressionSyntax
that has syntax for the CommandText
property as its Left
expression.
You can find that syntax from the Locations
property of the SymbolCallerInfo reference
you have, but in my experience it's easier to work the other way around.
You're looking for text being assigned to a property, so I would search for AssignmentExpressionSyntax
.
In an Analyzer, you would override the Initialize
method as follows:
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.RegisterSyntaxNodeAction(
AnalyzeAssignmentExpression, SyntaxKind.SimpleAssignmentExpression);
}
Note that we're only looking for simple assignments here; i.e. assignments using =
. I suppose you could consider looking for SyntaxKind.AddAssignmentExpression
as well, i.e. assignments using +=
, but it would be less easy to determine what exactly is the value being assigned, so I left that out.
So now we need to implement the AnalyzeAssignmentExpression
method.
private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)
The first thing we do is get the AssignmentExpressionSyntax node
we're analyzing:
private void AnalyzeAssignmentExpression(SyntaxNodeAnalysisContext context)
{
var node = (AssignmentExpressionSyntax)context.Node;
That node has Left
and Right
properties we need to look at. Let's start with the Left
property, and find out what symbol this refers to.
ISymbol left = context.SemanticModel.GetSymbolInfo(
node.Left, context.CancellationToken).Symbol;
We need to find out if this Symbol is indeed our CommandText
property.
Here's a small helper method that will do just that:
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
}
}
}
}
}
};
(You could also compare its ContainingType
to a symbol you looked up once using Compilation.GetTypeByMetadataName()
, if you know the exact type you're looking for. I didn't, so I used this pattern.)
Using that helper method, we can test our left
symbol. If it's not our CommandText
property, we can stop analyzing this node
.
if (!IsCommandText(left))
{
return;
}
Now let's look at the Right
property, the syntax being assigned. You said you're looking for a LiteralExpressionSyntax
, but I would suggest you look at any syntax that evaluates to a constant string. It doesn't have to be a literal. It could be the name of a constant for example, a concatenation of two literals, or many other kinds of expressions. But I would think that all you really care about, is its value. So let's see if the Right
property has a constant string value. (I am assuming you're not interested is the value null
here.) If it has a constant string value, we have found what we're looking for.
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
}
}
The entire method:
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
}
}
If you're not doing this in an analyzer, you can find the AssignmentExpressionSyntax
nodes using root.DescendantNodes().OfType<AssignmentExpressionSyntax>()
, and from your question I take it you already know how to get the SemanticModel
for them.