How to insert a function into a class in VB.NET using Roslyn - roslyn

I have some logic to add a function to class using Roslyn, which works with a C# project, but not with a VB project. I am using the DocumentEditor editor class (Microsoft.CodeAnalysis.Editing.DocumentEditor) to perform the update.
I start by finding the SyntaxNode corresponding to the class definition.
In C# this is a ClassDeclarationSyntax element.
In VB this is a ClassBlockSyntax element.
I generate the complete text of the new function in a string variable, and then create a SyntaxNode from the text.
For C# I use the method CSharpSyntaxTree.ParseText, approximately as follows:
var Tree = CSharpSyntaxTree.ParseText ( Code, CSharpParseOptions.Default ) ;
var Root = await Tree.GetRootAsync() as CompilationUnitSyntax ;
var Expr = Root.Members.FirstOrDefault()
.WithAdditionalAnnotations ( Formatter.Annotation ) ;
Expr is then of type MethodDeclarationSyntax.
For VB I use the method VisualBasicSyntaxTree.ParseText, with almost identical code:
var Tree = VisualBasicSyntaxTree.ParseText ( Code ) ;
var Root = await Tree.GetRootAsync() as CompilationUnitSyntax ;
var Expr = Root.Members.FirstOrDefault() ;
In this case Expr is of type MethodBlockSyntax.
I then try to insert the new node into the class.
For C# I use
RoslynDocEditor.InsertAfter ( RoslynClass.ChildNodes.Last, Expr )
where RoslynClass is the ClassBlockSyntax node, and a little later ...
RootNode = RoslynDocEditor.GetChangedRoot()
RootNode = Formatter.Format ( RootNode, Formatter.Annotation, VSWorkspace )
RoslynDoc = RoslynDoc.WithSyntaxRoot ( RootNode )
ApplyOK = VSWorkspace.TryApplyChanges ( RoslynDoc.Project.Solution )
This adds the new function at the end of the class.
If I do the same for VB, it generates an InvalidOperationException at the line
RootNode = RoslynDocEditor.GetChangedRoot()
with the description "The item specified is not the element of a list" and the stack trace:
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitClassBlock(ClassBlockSyntax node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassBlockSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.BaseListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitListElement[TNode](TNode node)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitList[TNode](SyntaxList`1 list)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.VisitList[TNode](SyntaxList`1 list)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.VisitCompilationUnit(CompilationUnitSyntax node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.CompilationUnitSyntax.Accept[TResult](VisualBasicSyntaxVisitor`1 visitor)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxRewriter.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.BaseListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.NodeListEditor.Visit(SyntaxNode node)
at Microsoft.CodeAnalysis.VisualBasic.Syntax.SyntaxReplacer.InsertNodeInList(SyntaxNode root, SyntaxNode nodeInList, IEnumerable`1 nodesToInsert, Boolean insertBefore)
at Microsoft.CodeAnalysis.VisualBasic.VisualBasicSyntaxNode.InsertNodesInListCore(SyntaxNode nodeInList, IEnumerable`1 nodesToInsert, Boolean insertBefore)
at Microsoft.CodeAnalysis.SyntaxNodeExtensions.InsertNodesBefore[TRoot](TRoot root, SyntaxNode nodeInList, IEnumerable`1 newNodes)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator.InsertDeclarationsBeforeInternal(SyntaxNode root, SyntaxNode declaration, IEnumerable`1 newDeclarations)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator._Closure$__310-0._Lambda$__0(SyntaxNode r)
at Microsoft.CodeAnalysis.Editing.SyntaxGenerator.PreserveTrivia[TNode](TNode node, Func`2 nodeChanger)
at Microsoft.CodeAnalysis.VisualBasic.CodeGeneration.VisualBasicSyntaxGenerator.InsertNodesBefore(SyntaxNode root, SyntaxNode declaration, IEnumerable`1 newDeclarations)
at Microsoft.CodeAnalysis.Editing.SyntaxEditor.InsertChange.Apply(SyntaxNode root, SyntaxGenerator generator)
at Microsoft.CodeAnalysis.Editing.SyntaxEditor.GetChangedRoot()
at MultiLang.frmLanguageSwitching.VB$StateMachine_133_btAdd_Click.MoveNext() in C:\VSPackage_Version_7_1\Project\MultiLang\Forms\frmLanguageSwitching.vb:line 944
From the screen shot of the Syntax Visualizer you can see that the last child of the ClassBlock is the EndClassStatement, so it makes more sense to use
RoslynDocEditor.InsertBefore ( RoslynClass.ChildNodes.Last, NewFunctionNode )
but that generates exactly the same error as above.
Is there a way to insert a function in VB class in a similar manner, or does this only work in C#.

It works for VB, if I cast the ClassNode from a SyntaxNode (back) to ClassBlockSyntax and then use the Members collection
var cbs = ClassNode as Microsoft.CodeAnalysis.VisualBasic.Syntax.ClassBlockSyntax ;
RoslynDocEditor.InsertAfter ( cbs.Members.Last(), Expr ) ;
The same works for C#, if I cast the ClassNode to ClassDeclarationSyntax
var cds = ClassNode as Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax ;
RoslynDocEditor.InsertAfter ( cds.Members.Last(), Expr ) ;
but as already described, for C# it also works with
RoslynDocEditor.InsertAfter ( RoslynClass.ChildNodes.Last, Expr ) ;
So it looks like SyntaxNode.ChildNodes is equivalent to ClassDeclarationSyntax.Members for C#, but not equivalent to ClassBlockSyntax.Members for VB.
EDIT
It looks like an easier and a better solution is to use the AddMember extension method:
RoslynDocEditor.AddMember ( RoslynClass, expr )

Related

GetSymbolInfo().Symbol returning null on AttributeSyntax

I have a custom attribute I'm using to test a Roslyn code analyzer I'm writing:
[AttributeUsage( validOn: AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = true )]
public class DummyAttribute : Attribute
{
public DummyAttribute( string arg1, Type arg2 )
{
}
public int TestField;
}
which decorates another test class:
[Dummy( "", typeof( string ) )]
[Dummy( "test", typeof( int ) )]
[Dummy( "test", typeof( int ) )]
public class J4JLogger<TCalling> : IJ4JLogger<TCalling>
{
But when I call GetSymbolInfo() on it with the semantic model it's defined in:
model.GetSymbolInfo( attrNode ).Symbol
the value of Symbol is null.
What's odd is that the GetSymbolInfo() call works perfectly well for attribute classes defined in the Net Core library (e.g., AttributeUsage).
Both Dummy and J4JLogger are defined in the same project. I create my compilation unit by parsing the files individually (e.g., J4JLogger is parsed and analyzed separately from Dummy) so when I'm parsing J4JLogger there is no reference to the assembly containing both J4JLogger and Dummy.
Could the problem be that model doesn't actually contain the Dummy class I think it does? Is there a way to check what's in the semantic model? Do I have to include a reference to the assembly whose source file I'm analyzing in the semantic model?
Corrected Parsing Logic
My original parsing logic parsed each file into a syntax tree independent of all its sister source files. The correct way to parse source files -- at least when they depend on each other -- is something like this:
protected virtual (CompilationUnitSyntax root, SemanticModel model) ParseMultiple( string primaryPath, params string[] auxPaths )
{
if( !IsValid )
return (null, null);
CSharpCompilation compilation;
SyntaxTree primaryTree;
var auxFiles = auxPaths == null || auxPaths.Length == 0
? new List<string>()
: auxPaths.Distinct().Where( p => !p.Equals( primaryPath, StringComparison.OrdinalIgnoreCase ) );
try
{
var auxTrees = new List<SyntaxTree>();
primaryTree = CSharpSyntaxTree.ParseText( File.ReadAllText( primaryPath ) );
auxTrees.Add( primaryTree );
foreach( var auxFile in auxFiles )
{
var auxTree = CSharpSyntaxTree.ParseText( File.ReadAllText( auxFile ) );
auxTrees.Add( auxTree );
}
compilation = CSharpCompilation.Create( ProjectDocument.AssemblyName )
.AddReferences( GetReferences().ToArray() )
.AddSyntaxTrees( auxTrees );
}
catch( Exception e )
{
Logger.Error<string>( "Configuration failed, exception message was {0}", e.Message );
return (null, null);
}
return (primaryTree.GetCompilationUnitRoot(), compilation.GetSemanticModel( primaryTree ));
}
A minor gotcha is that AddSyntaxTrees() does not appear to be incremental; you need to add all the relevant syntax trees in one call to AddSyntaxTrees().
Turns out the problem was that you have to include all the source files that reference each other (e.g., Dummy and J4JLogger in my case) in the compilation unit because otherwise the "internal" references (e.g., decorating J4JLogger with Dummy) won't resolve. I've annotated the question with how I rewrote my parsing logic.

Using Roslyn, how to enumerate members (Namespaces, classes etc) details in Visual Basic Document?

Using Roslyn, the only mechanism for determining members of Visual Basic document appears to be:
var members = SyntaxTree.GetRoot().DescendantNodes().Where(node =>
node is ClassStatementSyntax ||
node is FunctionAggregationSyntax ||
node is IncompleteMemberSyntax ||
node is MethodBaseSyntax ||
node is ModuleStatementSyntax ||
node is NamespaceStatementSyntax ||
node is PropertyStatementSyntax ||
node is SubNewStatementSyntax
);
How do get the member name, StarLineNumber and EndLineNumber of each member?
Exists not only the one way to get it:
1) As you try: I willn't show this way for all of kind member (they count are huge and the logic is the similar), but only a one of them, for example ClassStatementSyntax:
to achive it name just get ClassStatementSyntax.Identifier.ValueText
to get start line you can use Location as one of ways:
var location = Location.Create(SyntaxTree, ClassStatementSyntax.Identifier.Span);
var startLine = location.GetLineSpan().StartLinePosition.Line;
logic for retrieving the end line looks like a logic to receive the start line but it dependents on the corresponding closing statement (some kind of end statement or self)
2) More useful way – use SemanticModel to get a data that you want:
In this way you will need to receive semantic info only for ClassStatementSyntax, ModuleStatementSyntxt and NamespaceStatementSyntax, and all of their members will be received just calling GetMembers():
...
SemanticModel semanticModel = // usually it is received from the corresponding compilation
var typeSyntax = // ClassStatementSyntax, ModuleStatementSyntxt or NamespaceStatementSyntax
string name = null;
int startLine;
int endLine;
var info = semanticModel.GetSymbolInfo(typeSyntax);
if (info.Symbol is INamespaceOrTypeSymbol typeSymbol)
{
name = typeSymbol.Name; // retrieve Name
startLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol.DeclaringSyntaxReferences[0].Span).StartLinePosition.Line; //retrieve start line
endLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol.DeclaringSyntaxReferences[0].Span).EndLinePosition.Line; //retrieve end line
foreach (var item in typeSymbol.GetMembers())
{
// do the same logic for retrieving name and lines for all others members without calling GetMembers()
}
}
else if (semanticModel.GetDeclaredSymbol(typeSyntax) is INamespaceOrTypeSymbol typeSymbol2)
{
name = typeSymbol2.Name; // retrieve Name
startLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol2.DeclaringSyntaxReferences[0].Span).StartLinePosition.Line; //retrieve start line
endLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol2.DeclaringSyntaxReferences[0].Span).EndLinePosition.Line; //retrieve end line
foreach (var item in typeSymbol2.GetMembers())
{
// do the same logic for retrieving name and lines for all others members without calling GetMembers()
}
}
But attention, when you have a partial declaration your DeclaringSyntaxReferences will have a couple items, so you need to filter SyntaxReference by your current SyntaxTree

parse and replace a list of object in kotlin

I am currently having a list of obeject defined as:
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
... code below
}
the Tool data class is defined as:
data class Tool(
var id: String = ""
var description: String = ""
var assignedTo: String = ""
)
the Updated data class is defined as:
data class Updated(
var id: String = ""
var assignedTo: String = ""
)
Basically, I parse the list updateTools and if I found a id match in tools, I update the assignedTo field from the Tool type object from tools by the one from updateTools
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
updateTools.forEach{
val idToSearch = it.id
val nameToReplace = it.name
tools.find(){
if(it.id == idToSearch){it.name=nameToReplace}
}
}
return tools
}
it's not working but I do not see how to make it easier to work. I just started kotlin and I feel that it's not the good way to do it
any idea ?
Thanks
First of all:
you're not assigning assignedTo, you're assigning name...
in the predicate passed to find, which
should only return a Boolean value to filter elements, and
should probably not have any side effects,
those should be done later with a call to i.e. forEach.
Additionally, your constructor parameters to the data class are normal parameters, and as such, need commas between them!
Your last code block, corrected, would be:
updateTools.forEach {
val idToSearch = it.id
val nameToReplace = it.name
tools.find { it.id == idToSearch }.forEach { it.assignedTo = nameToReplace }
}
return tools
I'd do it like this (shorter):
updateTools.forEach { u -> tools.filter { it.id == u.id }.forEach { it.assignedTo = u.name } }
This loops through each update, filters tools for tools with the right ID, and sets the name of each of these tools.
I use forEach as filter returns a List<Tool>.
If you can guarantee that id is unique, you can do it like this instead:
updateTools.forEach { u -> tools.find { it.id == u.id }?.assignedTo = u.name }
firstOrNull returns the first element matching the condition, or null if there is none. Edit: it seems find is firstOrNull - its implementation just calls firstOrNull.
The ?. safe call operator returns null if the left operand is null, otherwise, it calls the method.
For = and other operators which return Unit (i.e. void, nothing), using the safe call operator simply does nothing if the left operand is null.
If we combine these, it effectively sets the name of the first element which matches this condition.
First, you're missing comma after properties in your data classes, so it should be:
data class Tool(
var id: String = "",
var description: String = "",
var assignedTo: String = ""
)
data class Updated(
var id: String = "",
var assignedTo: String = ""
)
As for second problem, there're probably number of ways to do that, but I've only corrected your idea:
fun updateList(tools: List<Tool>, updateTools: List<Updated>): List<Tool> {
updateTools.forEach{ ut ->
tools.find { it.id == ut.id }?.assignedTo = ut.assignedTo
}
return tools
}
Instead of assigning values to variables, you can name parameter for forEach and use it in rest of the loop.

parsing comment in tinyXML2

I have problem with parsing XML comment. How can i properly access to comment?
Or is even possible to read comment with tinyXML2?
<xml>
<foo> Text <!-- COMMENT --> <foo2/></foo>
</xml>
I created
XMLElement *root = xmlDoc->FirstChildElement("foo");
XMLElement *child = root->FirstChildElement();
From child element i get foo2 element, What is propper way to read comment element from file.
Thanks
You can use XMLNode::FirstChild() and XMLNode::NextSibling() to loop through all child nodes. Use dynamic_cast to test if node is a comment.
if( const XMLElement *root = xmlDoc->FirstChildElement("foo") )
{
for( const XMLNode* node = root->FirstChild(); node; node = node->NextSibling() )
{
if( auto comment = dynamic_cast<const XMLComment*>( node ) )
{
const char* commentText = comment->Value();
}
}
}
I've made this up just from reading the documentation, so there might be mistakes in the code.
I just created a function on my project that navigates the entire document recursively and get rid of comments. You can use that to see how you can pick up any comment on the document... followed the example of the fellow above..
Code bellow:
// Recursively navigates the XML and get rid of comments.
void StripXMLInfo(tinyxml2::XMLNode* node)
{
// All XML nodes may have children and siblings. So for each valid node, first we
// iterate on it's (possible) children, and then we proceed to clear the node itself and jump
// to the next sibling
while (node)
{
if (node->FirstChild() != NULL)
StripXMLInfo(node->FirstChild());
//Check to see if current node is a comment
auto comment = dynamic_cast<tinyxml2::XMLComment*>(node);
if (comment)
{
// If it is, we ask the parent to delete this, but first move pointer to next member so we don't get lost in a NULL reference
node = node->NextSibling();
comment->Parent()->DeleteChild(comment);
}
else
node = node->NextSibling();
}
}

Doctrine 2 nested set - retrieve full tree in single query

I'm using stof/StofDoctrineExtensionsBundle (Bundle wrapper for Atlantic18/DoctrineExtensions) to implement a Nested Set (tree) entity. The entity is configured and working, but I can't figure out how to retrieve all root notes with all of their children (full trees) in a single query. I currently have the full collection returning however it lazy loads all children, meaning a large number of queries is performed.
Thanks for any help.
You can just use childrenHierarchy() method for whole tree retrieve:
$tree = $repo->childrenHierarchy();
Found a solution.
retrieve full list of node objects:
$repo = $this->getDoctrine()->getManager()->getRepository('NestedEntity');
$nodes = $repo->getChildren();
build tree with your nodes.
$tree = $repo->getRepoUtils()->buildTreeArray($nodes);
buildTreeArray method accepts array of node-arrays, so you must implement ArrayAccess interface in your Entity. Also it puts all children in __children key of node-array.
/**
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="nested_entity")
* #ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")
*/
class NestedEntity implements \ArrayAccess
{
public function offsetExists($offset)
{
return property_exists($this, $offset);
}
public function &offsetGet($offset)
{
return $this->$offset;
}
public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
$this->$offset = null;
}
protected $__children = [];
...