How to get a Roslyn FieldSymbol from a FieldDeclarationSyntax node? - c#

I'm trying to use Roslyn to determine the publically-exposed API of a project (and then do some further processing using this information, so I can't just use reflection). I'm using a SyntaxWalker to visit declaration syntax nodes, and calling IModel.GetDeclaredSymbol for each. This seems to work well for Methods, Properties, and Types, but it doesn't seem to work on fields. My question is, how do I get the FieldSymbol for a FieldDeclarationSyntax node?
Here's the code I'm working with:
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
var model = this._compilation.GetSemanticModel(node.SyntaxTree);
var symbol = model.GetDeclaredSymbol(node);
if (symbol != null
&& symbol.CanBeReferencedByName
// this is my own helper: it just traverses the publ
&& symbol.IsExternallyPublic())
{
this._gatherer.RegisterPublicDeclaration(node, symbol);
}
base.VisitFieldDeclaration(node);
}

You need to remember that a field declaration syntax can declare multiple fields. So you want:
foreach (var variable in node.Declaration.Variables)
{
var fieldSymbol = model.GetDeclaredSymbol(variable);
// Do stuff with the symbol here
}

Related

Modifying function declaration parameter by adding attribute and default value in Roslyn c#

I'm modifying my earlier code analyser in C# using Roslyn and again I'm stuck with some changes that I don't exactly know how to apply.
Basing on: https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Syntax-Analysis I've created some base to work with in this question: Finding all not inheriting C# classes with Roslyn and changing to inheriting from base object (java-like)
I've parsed and traversed tree to find all method declarations, and their parameters. Basing on VS Syntax Visualiser I've constructed this:
foreach (var c in root1.DescendantNodesAndSelf())
{
var methodDeclaration = c as MethodDeclarationSyntax;
if (methodDeclaration == null)
continue;
if (methodDeclaration.ParameterList != null) //Have parameters
{
foreach (var p in methodDeclaration.ParameterList.Parameters)
{
var parameter = p as ParameterSyntax;
String name, type;
name = parameter.GetLastToken().Value.ToString();
type = parameter.GetFirstToken().Value.ToString();
if (parameter == null)
continue;
if (name == "caller" && type == "string")
{
AttributeSyntax ats = SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System.Runtime.CompilerServices"),SyntaxFactory.IdentifierName("CallerMemberName")));
SeparatedSyntaxList<AttributeSyntax> ssl = new SeparatedSyntaxList<AttributeSyntax>();
ssl = ssl.Add(ats);
AttributeListSyntax als = SyntaxFactory.AttributeList(ssl);
var par1 = parameter.AddAttributeLists(als);
//ExpressionSyntax es = SyntaxFactory.AssignmentExpression(SyntaxKind.EqualsValueClause,null,SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression))
//SyntaxFactory.EqualsValueClause(es);
par1 = par1.AddModifiers();
root2 = root2.ReplaceNode(parameter, par1);
}
}
}
else //Don't have parameters
continue;
}
I'm trying to convert method declared like this:
private void testM3(string caller)
into
private void testM3([System.Runtime.CompilerServices.CallerMemberName] string caller = "")
And this part:
//ExpressionSyntax es = SyntaxFactory.AssignmentExpression(SyntaxKind.EqualsValueClause,null,SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression))
//SyntaxFactory.EqualsValueClause(es);
is my failed attempt to achieve creation of equals node.
From what I understand, this part:
AttributeSyntax ats = SyntaxFactory.Attribute(SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System.Runtime.CompilerServices"),SyntaxFactory.IdentifierName("CallerMemberName")));
SeparatedSyntaxList<AttributeSyntax> ssl = new SeparatedSyntaxList<AttributeSyntax>();
ssl = ssl.Add(ats);
AttributeListSyntax als = SyntaxFactory.AttributeList(ssl);
var par1 = parameter.AddAttributeLists(als);
will give me new parameter node in var par1 that includes attribute already, so I need to add default value setting. Please correct me if I'm wrong about this attribute, and I'd like to know how to build this equals expression node properly.
You have a two mistakes:
System.Runtime.CompilerServices.CallerMemberName is QualifiedName that contains System.Runtime.CompilerServices as QualifiedName and CallerMemberName as IdentifierName. System.Runtime.CompilerServices contains System.Runtime as QualifiedName and CompilerServices as IdentifierName. And finally System.Runtime contains two IdentifierName.
So you need to fix creation of AttributeSyntax as are shown in the code below:
AttributeSyntax ats = SyntaxFactory.Attribute(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(
SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Runtime")),
SyntaxFactory.IdentifierName("CompilerServices")),
SyntaxFactory.IdentifierName("CallerMemberName")));
EqualsValueClause shouldn't contain AssignmentExpression, but should contains a some kind of LiteralExpression directly. In your case it's StringLiteralExpression:
var par1 = parameter
.AddAttributeLists(als)
.WithDefault(SyntaxFactory.EqualsValueClause(SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(""))));
By the way, you can use a some helpful method of Nodes, as example ParameterSyntax.WithDefault, to create a copy of Node (SyntaxTree is immutable in Roslyn) when you want to applay a small changes to existing node and then repace it.
If you take a look at the Roslyn Quoter for the following method you can obtain the code required to generate the desired code:
public void GetSomething([CallerMemberName] string test=""){
}
You will notice that the Default Value in the Parameter is constructed using the following method (the roslyn quoter usually omits the SyntaxFactory):
.WithDefault(SyntaxFactory.EqualsValueClause(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("")
)
)
);
So in order to add your EqualsValueClause as a default you just have to replace the existing default by calling the above code on your parameter (instead of the uncommented code):
par1 = par1.WithDefault(SyntaxFactory.EqualsValueClause(
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("")
)
)
);

Roslyn get IdentifierName in ObjectCreationExpressionSyntax

Currently I am working on simple code analyse for c# with roslyn. I need to parse all document of all projects inside one solution and getting the declared used classes inside this document.
For example from:
class Program
{
static void Main(string[] args)
{
var foo = new Foo();
}
}
I want to get Program uses Foo.
I already parse all documents and get the declared class inside.
// all projects in solution
foreach (var project in _solution.Projects)
{
// all documents inside project
foreach (var document in project.Documents)
{
var syntaxRoot = await document.GetSyntaxRootAsync();
var model = await document.GetSemanticModelAsync();
var classes = syntaxRoot.DescendantNodes().OfType<ClassDeclarationSyntax>();
// all classes inside document
foreach (var classDeclarationSyntax in classes)
{
var symbol = model.GetDeclaredSymbol(classDeclarationSyntax);
var objectCreationExpressionSyntaxs = classDeclarationSyntax.DescendantNodes().OfType<ObjectCreationExpressionSyntax>();
// all object creations inside document
foreach (var objectCreationExpressionSyntax in objectCreationExpressionSyntaxs)
{
// TODO: Get the identifier value
}
}
}
}
The problem is to get the IdentifierName Foo. Using the debugger, I see objectCreationExpressionSyntax.Typegot the Identifier.Text got the value I need, but objectCreationExpressionSyntax.Type.Identifierseems to be private.
I could use the SymbolFinder to find all references of a Class in the solution. As I already need to parse all documents its should work without.
Maybe I am on the wrong path? How to get the identifier value?
You'll need to handle the different types of TypeSyntaxes. See here: http://sourceroslyn.io/#Microsoft.CodeAnalysis.CSharp/Syntax/TypeSyntax.cs,29171ac4ad60a546,references
What you see in the debugger is a SimpleNameSyntax, which does have a public Identifier property.
Update
var ns = objectCreationExpressionSyntax.Type as NameSyntax;
if (ns != null)
{
return ns.Identifier.ToString();
}
var pts = objectCreationExpressionSyntax.Type as PredefinedTypeSyntax;
if (pts != null)
{
return pts.Keyword.ToString();
}
...
All other subtypes would need to be handed. Note that ArrayType.ElementType is also a TypeSyntax, so you would most probably need to make this method recursive.
You can get the identifier from the syntax's Type property:
foreach (var objectCreationExpressionSyntax in objectCreationExpressionSyntaxs)
{
IdentifierNameSyntax ins = (IdentifierNameSyntax)objectCreationExpressionSyntax.Type;
var id = ins.Identifier;
Console.WriteLine(id.ValueText);
}
Strings can be misleading.
Let's say you have the expression new SomeClass(), and you get the string "SomeClass" out of it. How do you know if that refers to Namespace1.SomeClass or Namespace2.SomeClass ? What if there is a using SomeClass = Namespace3.SomeOtherType; declaration being used?
Fortunately, you don't have to do this analysis yourself. The compiler can bind the ObjectCreationExpressionSyntax to a symbol. You have your semantic model, use it.
foreach (var oce in objectCreationExpressionSyntaxs)
{
ITypeSymbol typeSymbol = model.GetTypeInfo(oce).Type;
// ...
}
You can compare this symbol with the symbols you get from model.GetDeclaredSymbol(classDeclarationSyntax), just make sure you use the Equals method, not the == operator.

Determine if a property is an auto property

I'm trying to figure out if a property is an auto property i.e.
public int Foo { get; set; }
Stared for a while at PropertyDeclarationSyntax and IPropertySymbol but did not find anything.
Guess an alternative is an extension method that evaluates if get & set does not contain any statements is one way but it does not feel very elegant.
Check whether any of the AccessorDeclarationSyntaxes in the PropertyDeclarationSyntax's AccessorList have a non-null Body.
You can see this by looking at any property declaration using the Syntax Visualizer (from the Roslyn SDK extension).
This blog gives a good explanation:
In summary,
var isExplicitProperty = node
.DescendantNodesAndSelf()
.OfType<PropertyDeclarationSyntax>()
.Any(prop =>
{
if(prop.AccessorList != null)
{
return prop.AccessorList.Accessors
.Any(a => a.Body != null || a.ExpressionBody != null);
}
// readonly arrow property declaration
return true;
});
Based on the internal source code

Trouble with XElement Extension Method [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 8 years ago.
I'm using the below code to pull out info from an XML file. But if node isn't present I get a NullReferenceException error. I thought that wasn't supposed to happen with using LINQ. But I'm very new to LINQ and XML for that matter. So I added an extention method I found from here. But I still get the error. Can someone tell me what I'm missing please?
using System;
using System.Text;
using System.Windows.Forms;
using System.Linq;
using System.Xml.Linq;
public static class XElementExtensionMethod
{
public static string ElementValueNull(this XElement element)
{
if (element != null)
{
return element.Value;
}
else
{
return string.Empty;
}
}
}
namespace XMLReader
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
private void frmMain_Load(object sender, EventArgs e)
{
string file = #"c:\users\jim\desktop\XMLData - Copy.xml";
XElement doc = XElement.Load(file);
StringBuilder sb = new StringBuilder();
var nodes = from node in doc.Elements("ClaimsSvcRs").Elements("ClaimDownloadRs")
select new
{
ClaimProbableAmount = (string)node.Element("ClaimsDownloadInfo").Element("ClaimsOccurrence").Element("ProbableIncurredAmt").Element("Amt").ElementValueNull()
};
foreach (var node in nodes)
{
sb.Append("AMOUNT = ").Append(node.ClaimProbableAmount);
MessageBox.Show(sb.ToString());
}
}
}
}
This is a problem:
ClaimProbableAmount = (string)node.Element("ClaimsDownloadInfo").Element("ClaimsOccurrence").Element("ProbableIncurredAmt").Element("Amt").ElementValueNull()
Since you're using Element(), you run the risk of getting the NRE. Element() returns the first instance of the named element or null. You've jumped too far down the rabbit hole.
There are a number of ways you can fix this, change all calls except the last to use Elements() instead. This time it will just return an empty collection if they don't exist. Or use other approaches such as an equivalent xpath query.
The problem is almost certainly not in ElementValueNull... it's that one of the earlier Element calls is returning null. You're calling an instance method (XContainer.Element()) and that's a perfectly ordinary instance method - if it's called on a null reference, that will throw an exception just like any other instance method will.
One option to avoid this is to use Elements repeatedly instead - that way you'll end up with an empty sequence, instead of a null reference. Use FirstOrDefault at the end to get a single XElement reference or null.
Additionally:
There's no benefit in using an anonymous type when you've got a single property. Just select the string itself.
There's no benefit in using a StringBuilder when you're calling ToString() on each iteration of the loop.
I would write your query as:
var amounts = doc.Elements("ClaimsSvcRs").Elements("ClaimDownloadRs")
.Select(x => (string) x.Elements("ClaimsDownloadInfo")
.Elements("ClaimsOccurrence")
.Elements("ProbableIncurredAmt")
.Elements("Amt")
.FirstOrDefault() ?? "");
(Note the lack of a need for your extension method - the explicit conversion to string will return null if the input is null, and then the null-coalescing operator will take care of using "" instead of null.)
If any of the parent elements are null (i.e. ClaimsDownloadInfo, ClaimsOccurance, etc), then you'll get a NullReferenceException. The ElementValueNull extension method won't magically capture that. You'll have to get each element one at a time and confirm that each is not null:
var claimsDownloadInfo = node.Element("ClaimsDownloadInfo");
if(claimsDownloadInfo != null)
{
var claimsOccurance = claimsDownloadInfo.Element("ClaimsOccurrence");
if(claimsOccurance != null)
{
//etc
}
}

Roslyn: Check if method parameter can not be null

Using Roslyn, my aim is to check if a method parameter is checked for not being null before the parameter is dereferenced. This check can be in a submethod of course.
My approach is to get the first dereferencing of the parameter and search the syntax tree between that and the method start for null checks. How can I do some kind of control flow analysis to determine if the first dereferencing of the parameter can be reached with the parameter being null?
This is way too broad question, with a little explanation what is your final goal. Are you trying to detect null-pointer exceptions before they even happen, 100%? (Pretty much impossible)
I have written static analysis myself few months ago, I didn't use roslyn, but this doesn't matter.
Check this out to get you possibly started - it's reporting warnings when there are unused variables:
internal class UnUsedVariableWarningDefinition : ICodeIssue
{
public IEnumerable<IssueReport> Analyze()
{
var usageMap = new Dictionary<string, int>(StringComparer.InvariantCultureIgnoreCase);
var variableMap = new Dictionary<string, IdentifierNode>(StringComparer.InvariantCultureIgnoreCase);
foreach (var node in NodeAnalyzerHelper.FindNodesDfs(Root))
{
var assignmentNode = node as AssignmentNode;
if (assignmentNode != null)
{
var variableNode = assignmentNode.Identifier;
int usages;
if (!usageMap.TryGetValue(variableNode.Identifier, out usages))
{
usageMap[variableNode.Identifier] = 0;
variableMap[variableNode.Identifier] = variableNode;
}
}
else
{
// not really an assignmentNode,
// let's see if we have detected the usage of IdentifierNode somewhere.
var variableNode = node as IdentifierNode;
if (variableNode != null)
{
if (usageMap.ContainsKey(variableNode.Identifier))
usageMap[variableNode.Identifier]++;
}
}
}
foreach (var node in usageMap.Where(x => x.Value == 0).Select(x => variableMap[x.Key]))
{
yield return node.ConstructWarning("No usages of this variable found. Are you sure this is needed?");
}
}
}
Notice that FindNodesDfs() is basically a syntax tree walker, which walks syntax nodes depth-first style. What it does is just scans AssigfnmentNodes and puts them to Dictionary, as soon as it identifies IdentifierNode, it checks the dictionary if it has previously encountered assignment, or not. It's a bit similiar what you're trying to do, I guess.

Categories

Resources