Roslyn - how to add statements after matching InvocationExpressionSyntax - c#

I am using Roslyn library.
I want to add the statements after matching line: Here is the requirement.
first I want to find the below line:
_container.RegisterInstance(NavigationService);
And then I want to add below statements after the above line:
_container.RegisterInstance<ISessionStateService>(SessionStateService);
_container.RegisterInstance<IFlyoutService>(FlyoutService);
Any help would be greatly appreciated.
EDIT:(I have created the expressions but now how to add those two experssions to my targetExpression?
string strContent = File.ReadAllText(strPath);
SyntaxTree tree = SyntaxTree.ParseText(strContent);
var targetExpression = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>()
.FirstOrDefault(
x => x.Expression.ToString().Contains("_container.RegisterInstance") && x.ArgumentList.ToString().Contains("NavigationService"));
InvocationExpressionSyntax replacementNode1 =
Syntax.InvocationExpression(Syntax.ParseExpression(#"_container.RegisterInstance<ISessionStateService>(SessionStateService);"));
InvocationExpressionSyntax replacementNode2 =
Syntax.InvocationExpression(Syntax.ParseExpression(#"_container.RegisterInstance<IFlyoutService>(FlyoutService);"));
MethodDeclarationSyntax targetMethod = (MethodDeclarationSyntax)targetExpression.Parent.Parent.Parent;
List<InvocationExpressionSyntax> list = targetMethod.DescendantNodes().OfType<InvocationExpressionSyntax>().ToList();
int index = list.IndexOf(targetExpression);
list.Insert(index + 1, replacementNode1);
list.Insert(index + 1, replacementNode2);
now the issue is how to get my updated tree?? Means how to update my list and get the tree with these changes.
Edit: Now I am able to generate add the nodes but only issue is formatting.. the spacing is not correct. here is the code:
string strContent = File.ReadAllText(strPath);
SyntaxTree tree = SyntaxTree.ParseText(strContent);
ExpressionStatementSyntax expressionStatementSyntax =
Syntax.ExpressionStatement(Syntax.ParseExpression("_container.RegisterInstance(NavigationService);"));
var targetBlock =
tree.GetRoot()
.DescendantNodes()
.OfType<BlockSyntax>()
.FirstOrDefault(x => x.Statements.Any(y => y.ToString().Contains("_container.RegisterInstance")));
StatementSyntax syn1 =
Syntax.ParseStatement(#"_container.RegisterInstance<ISessionStateService>(SessionStateService);");
StatementSyntax syn2 = Syntax.ParseStatement(#"_container.RegisterInstance<ISessionStateService>(SessionStateService2);");
List<StatementSyntax> newSynList = new List<StatementSyntax> { syn1, syn2 };
SyntaxList<StatementSyntax> blockWithNewStatements = targetBlock.Statements;
foreach (var syn in newSynList)
{
blockWithNewStatements = blockWithNewStatements.Insert(1, syn);
}
BlockSyntax newBlock = Syntax.Block(blockWithNewStatements);
var newRoot = tree.GetRoot().ReplaceNode(targetBlock, newBlock);
it generates the output with all the lines left aligned.. any suggestions?

After your edit, it looks like the main remaining question is about dealing with the line formatting. In both cases, once you've got your final root, you can invoke a formatter to clean it up. You have two options:
You can call the NormalizeWhitespace() extension on your nodes once you're done, which very rudely reformats all the nodes into something remotely "reasonable". If you don't care about preserving any formatting you had, and just want the output to not look terrible, this is the simple option.
You can reference the Roslyn.Services assembly, and then add "using Roslyn.Services" on the top if you don't already. From there, there's a Format() method which is a much fancier formatter that attempts to keep indenting as it is, respects what the user already had, etc, etc. If you stick a syntax annotation on the nodes you created, you can then pass that annotation to this formatter so it knows to leave the rest of the file untouched.

Related

In AngleSharp, how can I create DOM elements using string?

Is there a way to create DOM elements using string in AngleSharp? For example:
var result = document.Create...("<div id='div1'>hi<p>world</p></div>");
This is possible using a document fragment.
There are multiple possibilities how to use a document fragment, one way would be to use fragment parsing for generating a node list in the right context:
var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(r => r.Content("<div id=app><div>Some already available content...</div></div>"));
var app = document.QuerySelector("#app");
var parser = context.GetService<IHtmlParser>();
var nodes = parser.ParseFragment("<div id='div1'>hi<p>world</p></div>", app);
app.Append(nodes.ToArray());
The example shows how nodes can be created in the context of a certain element (#app in this case) and that the behavior is different than, e.g., using InnerHtml, which would remove existing nodes.
Hope this helps!

I am writing an application to extract different parts of a C# script

I am using the CSharpSyntaxTree and I have been able to extract the using statements, method and class definitions.The structure of my script is:
<using statements>
<executable code statements (may or may not be in a try-catch block)>
<method definitions>
<class definitions>
But now I am having an issue extracting all independent executable statements.
The line var statementSyntaxes = root.DescendantNodes().OfType<StatementSyntax>().ToList(); gives me all statements in the script but I am not interested in statements in the methods and class definitions. Please, how do I filter them out and retrieve only the statements that stand alone?
Here is my code:
var sourceCode = source.ToString();
SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode);
var root = (CompilationUnitSyntax)tree.GetRoot();
var members = root.Members;
// get all using statements
var usingCollector = new UsingCollector();
usingCollector.Visit(root);
var usingNames = String.Join("\n", usingCollector.Usings.Select(u => u.ToString()));
// get all methods
var methodDeclarationSyntaxes = root.DescendantNodes().OfType<MethodDeclarationSyntax>();
var usefulMethods = methodDeclarationSyntaxes.Where(m => m.Body != null);
var methodsScript = String.Join("\n", usefulMethods.Select(m => m.ToString()));
// get all classes
var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Select(c => c).ToList();
var classesScript = String.Join("\n", classDeclarations.Select(cc => cc.ToString()));
string customScript = $"{usingNames} \n{methodsScript} \n{classesScript}";
// get all executable statements
var statementSyntaxes = root.DescendantNodes().OfType<StatementSyntax>().ToList();
I am making an edit to the post:
So let's say I have a class with one method in it and I want to traverse through each of the executable statements in the method keeping in mind that some or all of these statements might be wrapped in a try-catch block. How do I achieve this, please?

Creating BlockSyntax from string (Roslyn)

I have some string with source code like
var newSource = #"
int a = 5;
int b = 10;
Console.WriteLine(a + b);";
I try to create BlockSyntax object with parsed code
var newTokens = SyntaxFactory.ParseTokens(newSource);
var newBody = SyntaxFactory.Block();
newBody = newBody.InsertTokensAfter(
newBody.OpenBraceToken, // or newBody.ChildTokens().First()
newTokens
);
But method InsertTokenAfter throws System.InvalidOperationException 'The item specified is not the element of a list.'
As I understand it, the method can not find a token in ChildTokens(), but why it happens?
.NET Core 1.0.4
The InsertTokensAfter method will not work for adding statements. It only allows you to insert tokens in an existing list of tokens (which is a specific construct that occurs in the C# syntax tree only in the modifier list of a declaration).
Statements are nodes. You can insert one or more nodes in a list of statements using InsertNodesAfter, but you would have to have an existing statement node in the list already to do this, and in your example you have an empty block that does not yet have any statements.
You could use the block.WithStatements() method, passing it a list of statements directly, if you had a list of statements. Unfortunately, there is no SyntaxFactory.ParseStatementList method.
There is, however, a SyntaxFactory.ParseStatement method, but that only parses one statement. Fortunately, a block is a statement. So you can just add a pair of braces around the source for your statements, and parse that as a block.
var block = (BlockSyntax)SyntaxFactory.ParseStatement("{" + newSource + "}");
During debug you can find a class
public abstract partial class CodeFixVerifier : DiagnosticVerifier
in the TestHelper namespace. Your code fails in the ApplyFix(Document document, CodeAction codeAction) method. I suppose the clue is in the document parameter: changes should be applied to the document, but your newBody is not attached yet.
If you are interested in the fix of your code - you can apply a code like
StatementSyntax newTokens = SyntaxFactory.ParseStatement(newSource);
BlockSyntax block = SyntaxFactory.Block(newTokens);

Generate Dynamic Linq query using outerIt

I am using Microsoft's Dynamic Linq (System.Linq.Dynamic) library to generate some queries at run time. This has worked great for me, but for one specific scenario.
Simplified scenario - I am trying to query all claims which have some specific tags that the user has selected and whose Balance is greater than some number.
static void Main(string[] args)
{
var claims = new List<Claim>();
claims.Add(new Claim { Balance = 100, Tags = new List<string> { "Blah", "Blah Blah" } });
claims.Add(new Claim { Balance = 500, Tags = new List<string> { "Dummy Tag", "Dummy tag 1" } });
// tags to be searched for
var tags = new List<string> { "New", "Blah" };
var parameters = new List<object>();
parameters.Add(tags);
var query = claims.AsQueryable().Where("Tags.Any(#0.Contains(outerIt)) AND Balance > 100", parameters.ToArray());
}
public class Claim
{
public decimal? Balance { get; set; }
public List<string> Tags { get; set; }
}
This query throws an error:
An unhandled exception of type 'System.Linq.Dynamic.ParseException' occurred in System.Linq.Dynamic.dll
Additional information: No property or field 'Balance' exists in type 'String'
Dynamic linq parser seems to try to find the Balance property on the Tag and not on the Claim object.
I have tried to play around with outerIt, innerIt, It keywords in Dynamic Linq but none of it seems to work.
Changing the sequence works, but that's not an option for me, since in the real application the filters, operators and patterns will be dynamic (configured by end user).
Boxing the conditions in brackets (), also doesn't help.
Workaround - create a simple contains condition for every Tag selected e.g. Tags.Contains("New") OR Tags.Contains("Blah") etc.. But in the real application it results in a really complex / bad query for each condition and kills the performance.
I might be missing something or this could be a bug in the library.
I would really appreciate if someone could help me with this.
Found a/the bug in ParseAggregate... The pushing of it→outerIt and back doesn't work if there are multiple levels. The code supposes that the it and outerIt won't be changed by a third party before being reset (technically the code isn't reentrant). You can try with other variants of System.Linq.Dynamic (there are like two or three variants out of there). Probably some variants have already fixed it.
Or you can take the code from the linked site and recompile it inside your code (in the end the "original" System.Linq.Dynamic is a single cs file) and you can patch it like this:
Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos)
{
// Change starts here
var originalIt = it;
var originalOuterIt = outerIt;
// Change ends here
outerIt = it;
ParameterExpression innerIt = Expression.Parameter(elementType, elementType.Name);
it = innerIt;
Expression[] args = ParseArgumentList();
// Change starts here
it = originalIt;
outerIt = originalOuterIt;
// Change ends here
MethodBase signature;
if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1)
I've already opened an Issue with the suggested bug fix in the github of the project.
This seems to be working correctly in my version: System.Linq.Dynamic.Core
See the test here:
https://github.com/StefH/System.Linq.Dynamic.Core/blob/master/test/System.Linq.Dynamic.Core.Tests/ComplexTests.cs#L19

How to add a property definition to existing type

If I want to add a new property to a type do I need to rebuild the whole type or is there a way to add property to existing type? Since one can only replace the nodes, do I have to replace the whole class declaration node? If so how would one do it?
Only way I have found to do this is dirty, and basically boils down to getting the source code of the type, finding the first open bracket, and inserting new source code for property, then parsing the resulting text and then replacing old class declaration node with a new one.
Something like this:
var toTypeSymbol =(TypeSymbol)compilation.GetTypeByMetadataName(propertyTypeInfo.ToString());
var toTypeDeclarationSyntax = (ClassDeclarationSyntax) toTypeSymbol.DeclaringSyntaxNodes.First();
var origToTypeCode = toTypeSymbol.DeclaringSyntaxNodes.First().ToFullString();
var idx = origToTypeCode.IndexOf("{")+1;
var newPropertyCode = String.Format(#" protected internal virtual {0} {0} {{get;set;}}",classSymbol.Name);
var newTypeCode = origToTypeCode.Insert(idx, newPropertyCode);
var newType = Syntax.ParseCompilationUnit(newTypeCode).NormalizeWhitespace();
var classDeclarationSyntax = newType.ChildNodes().OfType<ClassDeclarationSyntax>().First();
var temp = toTypeDeclarationSyntax.Parent.ReplaceNode(toTypeDeclarationSyntax,classDeclarationSyntax).NormalizeWhitespace();
Console.WriteLine(temp.ToFullString());
You could take a look at the ImplementINotifyPropertyChanged sample that is included in the CTP. One of the things it does is add an event, and a method. The same strategy applies to properties.

Categories

Resources