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);
Related
I have a CSharpSyntaxRewriter that overrides VisitMemberAccessExpression, inside that method, I am calling MemberAccessExpressionSyntax.WithName(), but the node it returns has a different SyntaxTree compared to the original node, this is a problem since it means an error is thrown when calling SemanticModel.GetSymbolInfo(node).
Is there a way to change the Name of a MemberAccessExpressionSyntax but still have a SyntaxTree that works with SemanticModel.GetSymbolInfo(node)?
My code is:
public sealed override SyntaxNode VisitNode(SyntaxNode node)
{
var nodeSyntax = (MemberAccessExpressionSyntax) node;
if (nodeSyntax.Name.ToString() == OldModifier && !HasSymbol(nodeSyntax, out _))
{
if (ModifierType == ModifierType.Damage)
nodeSyntax = nodeSyntax.WithName(IdentifierName($"GetDamage({NewModifier})"));
else
nodeSyntax = nodeSyntax.WithName(IdentifierName($"GetCritChance({NewModifier})"));
nodeSyntax = nodeSyntax.WithLeadingTrivia(node.GetLeadingTrivia()).WithTrailingTrivia(node.GetTrailingTrivia());
}
return nodeSyntax;
}
(VisitNode is called from VisitMemberAccessExpression)
And here are images showing the difference in the SyntaxTree:
original:
After calling WithName:
There's a few ways to approach something like this:
Change Your Visit Method
Can you restructure your code to call VisitNode before you've done any changes to VisitMemberAccessExpression? For example can you go from:
var memberAccess = memberAccess.With...();
memberAccess = VisitNode(memberAccess);
return memberAccess;
to
var memberAccess = VisitNode(memberAccess);
memberAccess = memberAccess.With...();
return memberAccess;
Then it might mean that you are then visiting with the original node before you've done rewriting. Note this can still be tricky though if you are doing recursive stuff.
Use ReplaceNodes
There's a helper method you can use instead of a rewriter. This will call the function you pass to do rewriting at each step, but that function is handed both the original node in the original tree and the node after child nodes have been rewritten; you can use the original one to ask binding questions (since that's from the original tree) and consume the one that's been rewritten for recursive rewriting.
Break Your Problem into Two Steps
Do two walks: first get a list of all the places will need to update, add those SyntaxNodes to a HashSet or like, and then do the rewrite second where you are updating those places.
In CodeFixProvider I need to remove wrapping if-condition (for example):
if (temp == null)
{
temp = new Temp();
}
and I want to leave only adjusted inner expression:
// I want to change the inner expression as well
temp = anotherTemp()
As soon as I attempt to replace nodes of 'if-block' with line-line statement, 'unable to cast' exception is thrown.
Do you know proper way to do it?
Only solution I have found out is to insert adjusted inner expression before if-condition block and remove that block afterwards.
To do that successfuly, you have to track parent block node, otherwise it wont work.
IfStatementSyntax originalIfStatement = parentIfStatement;
root = root.TrackNodes(originalParentIfStatement);
parentIfStatement = root.GetCurrentNode(originalParentIfStatement);
root = root.InsertNodesBefore(parentIfStatement, new[] { SyntaxFactory.ExpressionStatement(newExpression) });
parentIfStatement = root.GetCurrentNode(originalParentIfStatement);
root = root.RemoveNode(parentIfStatement, SyntaxRemoveOptions.KeepNoTrivia);
If anybodody see any mistake, please, tell me.
Below is the function I'm using to generate a simple method -
//NOTE : SF = SyntaxFactory
List<ParameterSyntax> parameterList = new List<ParameterSyntax>
{
SF.Parameter(SF.Identifier(sourceObjectName))
};
// Create method
var method = SF.MethodDeclaration(SF.ParseName(destinationClass), functionName)
.WithBody(SF.Block(nodes))
.AddModifiers(SF.Token(SyntaxKind.PublicKeyword))
.AddParameterListParameters(parameterList.ToArray())
.NormalizeWhitespace();
// NEED TO ADD PARAMS TO CODE
Console.WriteLine(method.GetText());
And here's the output:
public XYZ MapABCToXYZ(fromObject) // Should be 'ABC fromObject'
{
XYZ myObject = new XYZ();
myObject.MyProperty = fromObject.MyProperty;
myObject.TestProperty = fromObject.TestProperty;
return myObject;
}
As you can see, the parameter is not "ABC fromObject" and I've been trying to figure out the exact syntax to add parameters properly.
I've tried various ways to figure out the parameter syntax and have come up blank mostly.
EDIT: Figured it out. Just had to make a change in the following line:
SF.Parameter(SF.Identifier(sourceObjectName)).WithType(SF.ParseTypeName(sourceClass))
As suggested, I'm posting the solution here -
Figured it out. Just had to make a change in the following line:
SF.Parameter(SF.Identifier(sourceObjectName)).WithType(SF.ParseTypeName(sourceClass))
Where 'sourceClass' is a string of the required 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.
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.