Comments occupy a curious place within the ReSharper ecosystem. On the one hand, a comment may be a simple string encapsulated in a node of a physical tree. On the other hand, a comment preceding a method consist of valid XML that is subsequently represented as a proper data structure.
In this section, we’re going to talk about code comments, the ways in which they are represented and manipulated.
Before we begin, the most important thing to realize is that basic comments (as opposed to XML comments) are only part of the physical tree, i.e., comments are available in the tree structure that is the result of lexing of the source code, but they are not available when dealing with the definitions that are the results of the parsing stage.
Consider the following declaration:
From the lexical perspective, the class body can be treated essentially as a sequence of tokens such as a left curly brace, newline, and so on. The comment above is represented by an
ICommentNode structure with a
END_OF_LINE_COMMENT. Were you to change the comment to
/* this is a comment */, its type would be
But how do you actually get an
ICommentNode? This depends on the type of the feature you’re workin on. For example, let’s say that you’re writing a context action which replaces any comment it finds with
/* hello, world */. This context action would then have its
IsAvailable() method defined as follows:
TokenAfterCaret properties yield the tokens that appear before and after the caret in the file being edited. Detecting them is easy, but what about actually editing them?
The bad news is that you cannot just call some
SetText() method on a comment node and be done with it -- instead, you need to create a brand new
ICommentNode and replace the old one. Thus, the implementation of
ExecutePsiTransaction() for our context action would look something like the following:
In the above example, we use a
CSharpElementFactory to manufacture a brand new comment, then use
ModificationUtil to replace the old comment with the new one. If you wanted to simply get rid of the comment, you could use
Now, what would happen if you declared the comment as
/// <z>this is a comment</z>? The end result in this case is that the comment has a type of
DocComment. A ‘doc comment’ is different though -- unlike an ordinary comment, a doc comment is typically attached to a particular element of the ‘chemical’ tree, such as a class or class member (field, property, etc.). It’s also possible in certain cases for a doc comment to span several elements at once. Here is an example.
While the above may not be a best example in terms of programming, it does illustrate the fact that even a doc comment can affect more than one class member though, of course, in terms of the chemical tree, the two declared fields are collected under the umbrella of an
So now let’s get back to the usual questions. First, how do you determine that the caret is on a doc comment? The same way as before, except that the type of the node is different:
Now, you could always try and replace the doc comment wholesale (just like we did with simple comments), but instead of calling
CreateComment() on a
CSharpElementFactory, you have two separate methods that you can call for creating doc comments:
CreateDocComment()expects no line breaks in the specified parameter and simply prefixes the text with a
CreateDocCommentBlock()actually splits the parameters you provide into separate lines and then adds each one of them as part of a large block. Each line is, of course, prefixed with
The above methods may appear useful, but they have nothing to do with XML whatsoever. If you’re after manipulating XML, there’s an entirely different set of structures that you have to use.
XML Doc Comment Editing
Replacing the doc comment block completely is a pretty bad idea, because in most cases it’s up to you to keep the XML structure consistent. As a result, there’s a better way.
First of all, it’s worth explaining what the block part in
XmlCommentBlock means. Essentially, a block is simply a collection of XML comment nodes that sit next to one another. For example, you might have a
<summary> on one line and a
<param> on another: these lines together form part of a block.
A single doc comment is then part of that block. For example, a
<summary> would be a separate
IDocCommentNode that is part of a larger
IDocCommentBlockNode. Please note that a
IDocCommentBlockNode is created even if there is only one
DocComment in it.
Unlike basic comments, doc comment blocks are language-specific. This means that, if you’re working with C#, you’ll most likely be working with a
ICSharpDocCommentBlockNode. This type of node is a lot more interesting because, unlike the typical XML doc-related API which would yield you a simple
XmlNode (see e.g., the
IDeclaration.GetXmlDoc() method), the
ICSharpDocCommentBlockNode can yield you a fully fledged XML PSI interface:
Note the use of
Parent property in the above call: your caret is, essentially, on a doc comment node, whose parent is the block.
The great thing about
IDocCommentXmlPsi is that it contains a myriad of utility methods for adding or modifying particular XML doc declarations such as summary, parameters, exception information, and so on. For example, the following piece of code lets you add a
<summary> to the block comment:
Please note that the XML PSI interface is only available on existing XML doc comment blocks -- you cannot get this interface if you have an ordinary comment
/// like this one in your code. Thus, if you need to create an XML doc comment block from scratch, you should first use a
CSharpElementFactory to create the initial content for the block, and then get the
IDocCommentXmlPsi interface from it.