Modifying body of method with Roslyn - c#

Is there some way to add a specific statement at the end of a method in a .cs file with Roslyn?
var code = new StreamReader(#"C:\Users\PersonalUser\Documents\Visual Studio 2015\Projects\SampleToAnalyze\SampleToAnalyze\ClassChild.cs").ReadToEnd();
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree },
new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var model = compilation.GetSemanticModel(tree);
MethodDeclarationSyntax myMethod= tree.GetRoot().DescendantNodes()
.OfType<MethodDeclarationSyntax>().Last();
StatementSyntax myStatement= SyntaxFactory.ParseStatement(#"Console.WriteLine();");
I want to insert "myStatement" at the end of method "myMethod".

You can use the AddBodyStatements method to create a new MethodDeclarationSyntax with the statement added and then use ReplaceNode to update the SyntaxTree:
var newRoot = root.ReplaceNode(myMethod, myMethod.AddBodyStatements(myStatement));
This will create code that's valid C#, but looks wrong, because it's badly indented. Probably the simplest way to fix that is to also call Formatter.Format:
newRoot = Formatter.Format(newRoot, new AdhocWorkspace());

Related

Getting function parameters from Roslyn completion API

I was trying to use Completion API for building Autocomplete feature. The results are coming correctly but I am not able to get the method signatures.
For example: If I type "string.", API returns me all the methods like "Format","Compare" but after "string.Format(" it does not return all the possible method signatures like Format(String, Object), Format(String, Object[]) etc. Can someone help me in achieving that
var expression = "String.";
var code = String.Format(CultureInfo.InvariantCulture,
#"
Imports System
Sub Main(args As String[])
Dim typeUnderInvestigation As String = {0}
End sub
", expression);
var projectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), "MyProject", "MyProject", LanguageNames.VisualBasic)
.WithMetadataReferences(new[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
});
var project = workspace.AddProject(projectInfo);
var document = workspace.AddDocument(project.Id, "MyFile.cs", SourceText.From(code));
// position is the last occurrence of "Guid." in our test code
// in real life scenarios the editor surface should inform us
// about the current cursor position
var position = code.LastIndexOf(expression, StringComparison.InvariantCulture) + expression.Length;
var completionService = CompletionService.GetService(document);
var results = await completionService.GetCompletionsAsync(document, position);

Gremlin .Net, filter vertices by property containing a value

I need to filter vertices in Azure Cosmos Graph DB by a property containing a value, I tried the code below but I am getting an error says (Unable to find any method 'filter')
var g = client.CreateTraversalSource();
var p = new P("containing", text);
var query = g.V().Filter(p).Range<Vertex>(page, pageSize);
var result = await client.ExcuteAsync<IEnumerable<Vertex>>(query);
Any idea how to achieve this?
This might help someone else, I managed to figure it out with some help of a friend:
var p = new P("containing", text);
var query = g.V().has("propertyName", p).Range<Vertex>(page, pageSize);
var result = await client.ExecuteAsync<IEnumerable<Vertext>>(query);
In case anyone is still looking into this, there's predefined predicate values that can be used as string filters in the class TextP.
The above can be accomplished with the following:
var query = g.V().has("propertyName", TextP.Containing(text)).Range<Vertex>(page, pageSize);
var result = await client.ExecuteAsync<IEnumerable<Vertext>>(query);

How to generate initialization of class fields with Roslyn

I know how to create a local variable inside a method, for example this:
LocalDeclarationStatement(VariableDeclaration(IdentifierName("MyClass"))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier("nameOfvariable"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword)))))));
would give me:
MyClass nameOfvariable = new MyClass();
But say that I already created a field and now I simply want to initialize it (in a method, constructor or anything) like this:
nameOfVariable = new MyClass();
How do I do this? My guess it have to do with the VariableDeclerator but I can't find a way to get it right so I can add it to a list that contains StatementSyntaxes. I can change the VariableDecleration to "VariableDeclaration(IdentifierName(""))" too but that gives me an ugly extra space infront of the statement.
It seems like I struggle with some really basic stuff of Roslyn and I try to check http://roslynquoter.azurewebsites.net/ but that feels like the forced way to do it (feels like it create a lot more code than necessary).
Update: Should clarify that I know how to create method/constructors. I'm only looking for a way to initialize a field when I only have access to the field name and field type. So the only code I want to generate is this:
myField = new MyField();
Well you're almost there, you just need to create all that. This should do what you're interested in:
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var declaration = local.Declaration;
var declarator = declaration.Variables.First();
var identifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(identifier, SyntaxFactory.ArgumentList(), null);
var equalsValueClause = SyntaxFactory.EqualsValueClause(objectCreationExpression);
var newDeclarator = declarator.WithInitializer(equalsValueClause).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(declarator, newDeclarator);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
Some explanation: you create a new identifier MyClass which will be used in your ObjectCreationExpression. Then you wrap all that in an EqualsValueClause and you set that as an initializer to your declarator. We also add the Formatter annotation to this node so we can format it later and don't end up with whitespace issues.
All that's left then is replacing the node in your original tree, formatting it and you're done:
-------------------------------------------------------------------------------
If you instead mean that you want to put the assignment on its own separately from the declaration then you have to create a new AssignmentExpression and wrap it inside a ExpressionStatement. Typically expressions and statements are distinct concepts but this ExpressionStatement allows us to treat an expression as a statement which is important because a method's body only accepts statements.
In code, it looks like this:
internal static void Execute()
{
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable, another;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var method = local.Ancestors().OfType<MethodDeclarationSyntax>().First();
var variableIdentifier = SyntaxFactory.IdentifierName("nameOfVariable");
var classIdentifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(classIdentifier, SyntaxFactory.ArgumentList(), null);
var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, variableIdentifier, objectCreationExpression);
var expressionStatement = SyntaxFactory.ExpressionStatement(assignment).WithAdditionalAnnotations(Formatter.Annotation);
var newMethod = method.AddBodyStatements(expressionStatement);
var newRoot = root.ReplaceNode(method.Body, newMethod.Body);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
}
Result:
After some more trying and looking I found the answer. There is something called "AssignmentExpression" that you can use.
Here is an example how to use it:
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("myField"),
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword))));
This would give you:
myField = new Myclass();
So now it's easy to seperate creation and assignment/initialization to two different statements.
Note that I'm using "using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;" so I don't have to write SyntaxFactory all the time.
Or you can goto "http://roslynquoter.azurewebsites.net/" and paste your code in the small little textbox and click "Get Roslyn API calls to generate this code".
(I can generate the code you posted above, but it is kinda long. so i use a simple example.
For example, let's say you paste "DateTime mydate2 = new DateTime()", the tool will generate the following code :-
LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("DateTime"))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
Identifier("mydate2"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
IdentifierName("DateTime"))
.WithArgumentList(
ArgumentList())))))).WithSemicolonToken(
MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace()
Then you just have to fix up the code using SyntaxFactory, for example :-
var myDeclaratyion = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.IdentifierName("DateTime")).
WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("mydate2")).
WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.IdentifierName("DateTime"))
.WithArgumentList(
SyntaxFactory.ArgumentList())))))).WithSemicolonToken(SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace();

Update all properties of object in MongoDb

I'm using the MongoDB .Net driver in my project. I want to update all of the properties of my object that is stored in MongoDB. In the documentation, update is shown like this:
var filter = Builders<BsonDocument>.Filter.Eq("i", 10);
var update = Builders<BsonDocument>.Update.Set("i", 110);
await collection.UpdateOneAsync(filter, update);
But I don't want to call the Set method for all of the properties, since there are many properties and can be many more in the future.
How can I update the whole object using the MongoDB .Net driver?
You can do that with ReplaceOneAsync instead of UpdateOneAsync.
You need a filter to match the existing document (a filter with the document id is the simplest) and the new object.
Hamster hamster = ...
var replaceOneResult = await collection.ReplaceOneAsync(
doc => doc.Id == hamster.Id,
hamster);
var update = new BsonDocument("$set", new BsonDocument(entityType.GetProperties().Where(p => p.Name != "Id").Select(p => new KeyValuePair<string, object>(p.Name, entityType.GetProperty(p.Name).GetValue(task, null)))));
var options = new UpdateOptions();
collection.UpdateOne<MyTask>(item => item.Name == "cheque", update, options);
this code uses reflection to include all properties of the given object
to the update statement, no need to manually add all properties, as u see the Id is explicitly excluded from the update statement to avoid exception.
If you want to update your whole BsonDocument, there is an implicit conversion from BsonDocument to UpdateDefinition.
https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver/UpdateDefinition.cs
var doc = new BsonDocument() { .... }
UpdateDefinition<BsonDocument> update = doc;

Roslyn to insert nodes after specified node

I'm writing a code analyzer which inverts an if statement to reduce nesting.
I'm able to generate a new if node and replace it to the document root. However I must move all content(statements) coming from this if statement to below it. Let me show what I've achieved so far:
var ifNode = #if;
var ifStatement = #if.Statement as BlockSyntax;
var returnNode = (ifNode.Parent as BlockSyntax).Statements.Last() as ReturnStatementSyntax ?? SyntaxFactory.ReturnStatement();
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var invertedIf = ifNode.WithCondition(Negate(ifNode.Condition, semanticModel, cancellationToken))
.WithStatement(returnNode)
.WithAdditionalAnnotations(Formatter.Annotation);
var root = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = root.ReplaceNode(ifNode, invertedIf);
newRoot = newRoot.InsertNodesAfter(invertedIf, ifStatement.Statements); //It seems no to be working. There's no code after specified node.
return document.WithSyntaxRoot(newRoot);
Before:
public int Foo()
{
if (true)
{
var a = 3;
return a;
}
return 0;
}
After:
public int Foo()
{
if (false)
return 0;
var a = 3;
return a;
}
Carlos, the problem is that after you ReplaceNode you generated a new node. When you go InsertNodeAfter and pass a node from the original root node, the new node can't find it.
In an analyzer you need to either do all the changes at once, or annotate or track the nodes so you can come back to them later.
But since you are replacing a node first, the new node will be exactly at the same place. So you can shortcut and FindNode, like this:
newRoot = newRoot.InsertNodesAfter(newRoot.FindNode(ifNode.Span), ifStatement.Statements);
I haven't tested this code, but it should work.

Categories