How to get arguments of method to make completion with Roslyn? - c#

I try to make code completion with Roslyn and AvalonEdit.
For example, user have code:
public void Completion(int i,int j) { }
And he types:
Completion(
So, i want to get arguments of method (int i, int j) and make code completion.
I write simple code, wich works with '.' and may be this code will work for '(' letter?
public List<ICompletionData> GetCompletionData(String code,int offset,CompletionType completionType)
{
var syntaxTree = SyntaxFactory.ParseSyntaxTree(code);
var compilation = CSharpCompilation.Create("foo")
.AddReferences(Mscorlib)
.AddSyntaxTrees(syntaxTree);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var textSpan = GetTextSpan(offset,1);// '.' or '(' coordinates
ITypeSymbol lhsType = null;
if (completionType == CompletionType.DotCompletion)
{
var memberAccessNode = (MemberAccessExpressionSyntax)syntaxTree.GetRoot()
.DescendantNodes(textSpan).Last();
lhsType = semanticModel.GetTypeInfo(memberAccessNode.Expression).Type;
}
else if(completionType==CompletionType.ArgumentListCompletion)
{
var arr = syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var argumentListMode = (ArgumentListSyntax)syntaxTree.GetRoot().DescendantNodes(textSpan).Last();
var directive = argumentListMode.GetFirstDirective();
var arrgs=argumentListMode.Arguments;
//lhsType = semanticModel.GetTypeInfo(directive).Type;
//how to get lhsType?
}
if (lhsType == null)
return new List<ICompletionData>();
List<ICompletionData> completionDataColl = new List<ICompletionData>();
// and here i make completion data
foreach (var symbol in lhsType.GetMembers())
{
if (!symbol.CanBeReferencedByName
|| symbol.DeclaredAccessibility != Accessibility.Public
|| symbol.IsStatic)
continue;
}
}
The problem is, that i can not get ITypeSymbol lhsType. It is null.
How to get lhsType?
Or, maybe i should use another way?

I don't know the code completion (I couldn't find this class called CompletionType) itself, but here is a way based on Roslyn only: semantic model and the method invocation, which I believe you have available (make the method call string a InvocationExpressionSyntax)
To obtain the arguments of a method you can get its SymbolInfo from the semantic model. Then you get its symbol. A symbol contains the list of parameters(arguments).
You can call SemanticModel.GetSymbolInfo()
The result will provide you a symbol or candidate symbols (if it is an overloaded method).
A method symbol will provide the list of parameters, which is the arguments of that method.

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.

Extract arguments of method calls with Mono.Cecil

I am trying to extract all arguments passed to a specific method with Mono.Cecil.
In a post-processing script that runs right after the project being build, I am able to find all method calls, and even extract the type of the argument passed to the function. However, I am not able to get the actual value... So is this even possible with Mono.Cecil, and if yes, what value do I need to look at?
Here is my current code :
List<MethodDefinition> methods = new List<MethodDefinition> ();
foreach (ModuleDefinition _module in assembly.Modules) {
foreach (TypeDefinition _type in _module.Types) {
methods.AddRange (_type.Methods);
}
}
var uiExtensionMethod = methods
.Where(m => m.DeclaringType.Name == "SetCustomText").FirstOrDefault();
var instructions = uiExtensionMethod.Body.Instructions;
var count = instructions.Count;
var instructionArray = instructions.ToArray();
for (int i = 0; i < count; i++) {
if (instructionArray [i] != null) {
if (instructionArray [i].Operand != null) {
var paramType = instructionArray [i].Operand.ToString ();
// So here I have the type of the parameter, but I cannot get the value...
}
}
}
Okay, so I found the solution to this.
The problem was, that Mono.Cecil did indeed find my method calls, but it processed them inside the file, where the argument was already written into a variable, and therefor unable to be converted to a string.
So my solution is, parsing ALL methods that have a string as an Operand, and then detecting their NEXT operation. If the next operation is my function of choice, THEN I found the string I am looking for :)

How to find a method that matches declaration rules?

I need to find methods that match certain rules such as
has to be have return type void
has to be named "Set"
has to accept only one parameter
parameter type needs to match the type provided
I started going down the following route, but is just seems too much code. I wonder if there is a better way?
//find type name of the property
foreach (var propertySymbol in propertiesSymbols)
{
var propertyTypeSyntax =
((PropertyDeclarationSyntax) propertySymbol.DeclaringSyntaxNodes.First()).Type;
var propertyTypeInfo = semanticModel.GetTypeInfo(propertyTypeSyntax);
//find method with a name Set that accepts this type of the property
var allSetMethodsSymbols = classSymbol.GetMembers()
.Where(m => m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.ToList();
foreach (var setMethodSymbol in allSetMethodsSymbols)
{
var methodDeclarationSyntax =
((MethodDeclarationSyntax) setMethodSymbol.DeclaringSyntaxNodes.First());
var expressionSyntax =
methodDeclarationSyntax.DescendantNodes().OfType<ExpressionSyntax>().First();
var typeInfo = semanticModel.GetTypeInfo(expressionSyntax);
var typeName = typeInfo.Type.Name;
if (typeName == "Void")
{
//now we know it is a method named "Set" and has return type "Void"
//let's see if parameter matches
var parameterSymbols =
methodDeclarationSyntax.DescendantNodes().OfType<ParameterSyntax>()
.ToList();
if (parameterSymbols.Count() == 1)
{
//this one has one parameter
//now let's see if it is of the type needed
var exprSyntax = ((ParameterSyntax) parameterSymbols.First())
.DescendantNodes().OfType<ExpressionSyntax>().First();
var parameterTypeInfo = semanticModel.GetTypeInfo(exprSyntax);
if (parameterTypeInfo.Type.Equals(propertyTypeInfo.Type))
{
//execute method rewriter
}
}
}
}
}
Solution as suggested by Jason:
var propertyTypeInfo = propertySymbol.Type;
//find method with a name Set that accepts this type of the property
IEnumerable<MethodSymbol> allSetMethodsSymbols = classSymbol
.GetMembers()
.Where(m =>m.Kind == CommonSymbolKind.Method && m.Name.Equals("Set"))
.Cast<MethodSymbol>();
var setMethod = allSetMethodsSymbols
.Single(x => x.ReturnsVoid
&& x.Parameters.Count == 1
&& x.Parameters.First().Type == propertyTypeInfo);
Yes, you're switching back and forth between our symbol model and syntax, which is making this more difficult than it needs to be. Cast those symbol objects you are getting from GetMembers to MethodSymbol (once checking that they're a method). Once you've got the MethodSymbol, you can just check the .ReturnType property to get the return type -- don't go to syntax and re-get it that way. Or, just use the handy .ReturnsVoid property for your scenario. Similarly, MethodSymbol has a .Parameters property you can use to get the parameters -- don't go back to syntax for that either.

Getting generic arguments for SymbolCallerInfo in Roslyn

I'm attempting to find all locations in a solution that calls the method IBus.Publish<T> (from NServiceBus). So far this is working:
IMethodSymbol method = ... [IBus.Publish methodsymbol resolved];
var callers = method.FindCallers(solution, new CancellationToken());
This results in a IEnumerable<SymbolCallerInfo> and I get all the correct references to this method.
How would I now go about to get the generic argument IBus.Publish was called with? Do I have to parse the sourcetree manually, or does it exist some Roslyn magic I can leverage?
Example:
In my code I have:
IBus _bus;
_bus.Publish<IMyMessage>(msg => { msg.Text = "Hello world"});
I'm interested in getting the IMyMessage type.
Greatly appreciate the help!
You can use a SemanticModel to go from the SyntaxNode for the call to the actual MethodSymbol, and then you can just read the TypeArguments property to get the TypeSymbols for the arguments. That will even work if the arguments aren't specified explicitly, since the SemanticModel will perform type inference.
For example:
var callers = method.FindCallers(solution, CancellationToken.None);
foreach (var caller in callers)
{
foreach (var location in caller.Locations)
{
if (location.IsInSource)
{
var callerSemanticModel = solution
.GetDocument(location.SourceTree)
.GetSemanticModel();
var node = location.SourceTree.GetRoot()
.FindToken(location.SourceSpan.Start)
.Parent;
var symbolInfo = callerSemanticModel.GetSymbolInfo(node);
var calledMethod = symbolInfo.Symbol as IMethodSymbol;
if (calledMethod != null)
{
var arguments = calledMethod.TypeArguments;
}
}
}
}
I'm not really familiar with the IMethodSymbol and ISymbol interfaces, but here is an another way to get the generic arguments from a InvocationExpressionSyntax
var methodRef = (InvocationExpressionSyntax)find_method();
var genericArguments = methodRef.DescendantNodes().OfType<GenericNameSyntax>().FirstOrDefault();
if (genericArguments != null)
foreach (var g_arg in genericArguments.TypeArgumentList.Arguments)
Console.WriteLine(g_arg);
static InvocationExpressionSyntax find_method()
{
var code = new StreamReader("..\\..\\Tests.cs").ReadToEnd();
SyntaxTree tree = SyntaxTree.ParseText(code);
var root = tree.GetRoot();
//find your methods here
return (InvocationExpressionSyntax)root.DescendantNodes().ToArray()[88];
}

Categories

Resources