Skip to content

Commit f4e766e

Browse files
authored
C#: Add documentation comment LST infrastructure (#7015)
Add a structured LST for C# XML documentation comments (///) following the same architectural pattern as Java's Javadoc support: separate tree hierarchy, visitor with bidirectional CSharpVisitor bridge, and printer. Java side: - CsDocComment interface (extends Tree) with 8 node types: DocComment, XmlElement, XmlEmptyElement, XmlText, XmlAttribute, XmlCrefAttribute, XmlNameAttribute, LineBreak - CsDocCommentVisitor with bridge to CSharpVisitor for cref references - CsDocCommentPrinter for serializing back to /// XML format - CsDocCommentParser for converting raw text to structured tree - CsDocCommentRawComment for RPC-compatible raw comment transport - CSharpVisitor.visitSpace() override to detect and process doc comments C# side: - XmlDocComment class extending Comment for grouped /// lines - FormatWithComments groups consecutive /// lines into single XmlDocComment - CSharpPrinter handles XmlDocComment output - RPC type mappings for XmlDocComment <-> CsDocCommentRawComment
1 parent 926fede commit f4e766e

10 files changed

Lines changed: 1073 additions & 1 deletion

File tree

rewrite-csharp/csharp/OpenRewrite/CSharp/CSharpPrinter.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2877,7 +2877,12 @@ protected void VisitSpace(Space space, PrintOutputCapture<P> p)
28772877
p.Append(space.Whitespace);
28782878
foreach (var comment in space.Comments)
28792879
{
2880-
if (comment.Multiline)
2880+
if (comment is XmlDocComment)
2881+
{
2882+
// XmlDocComment text starts after "//" — printer prepends "//"
2883+
p.Append("//").Append(comment.Text);
2884+
}
2885+
else if (comment.Multiline)
28812886
{
28822887
p.Append("/*").Append(comment.Text).Append("*/");
28832888
}

rewrite-csharp/csharp/OpenRewrite/Core/Rpc/RpcReceiveQueue.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ private static bool IsTreeType(object obj)
441441
{
442442
"org.openrewrite.java.tree.Space" => typeof(Space),
443443
"org.openrewrite.java.tree.TextComment" => typeof(TextComment),
444+
"org.openrewrite.csharp.tree.CsDocCommentRawComment" => typeof(XmlDocComment),
444445
"org.openrewrite.marker.Markers" => typeof(Markers),
445446
"org.openrewrite.marker.SearchResult" => typeof(SearchResult),
446447
"org.openrewrite.marker.RecipesThatMadeChanges" => typeof(RecipesThatMadeChanges),

rewrite-csharp/csharp/OpenRewrite/Core/Rpc/RpcSendQueue.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ private void DoChange(object? after, object? before, Action? onChange, IRpcCodec
350350
"Markup" => "org.openrewrite.marker.Markup",
351351
"Space" => "org.openrewrite.java.tree.Space",
352352
"TextComment" => "org.openrewrite.java.tree.TextComment",
353+
"XmlDocComment" => "org.openrewrite.csharp.tree.CsDocCommentRawComment",
353354
"Checksum" => "org.openrewrite.Checksum",
354355
"FileAttributes" => "org.openrewrite.FileAttributes",
355356
_ => null,

rewrite-csharp/csharp/OpenRewrite/Core/Space.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,43 @@ public static Space FormatWithComments(string formatting)
185185
}
186186
}
187187

188+
// Group consecutive /// doc comments into a single XmlDocComment.
189+
// After rotation, doc comment lines are TextComment with text starting with "/".
190+
if (comments.Count > 0)
191+
{
192+
var grouped = new List<Comment>();
193+
int j = 0;
194+
while (j < comments.Count)
195+
{
196+
var cmt = comments[j];
197+
if (cmt is TextComment tc && !tc.Multiline && tc.Text.StartsWith("/"))
198+
{
199+
// Start of a doc comment block — group consecutive /// lines.
200+
var sb = new System.Text.StringBuilder();
201+
sb.Append(tc.Text);
202+
string lastSuffix = tc.Suffix;
203+
j++;
204+
while (j < comments.Count &&
205+
comments[j] is TextComment next && !next.Multiline &&
206+
next.Text.StartsWith("/"))
207+
{
208+
sb.Append(lastSuffix);
209+
sb.Append("//");
210+
sb.Append(next.Text);
211+
lastSuffix = next.Suffix;
212+
j++;
213+
}
214+
grouped.Add(new XmlDocComment(sb.ToString(), lastSuffix, true));
215+
}
216+
else
217+
{
218+
grouped.Add(cmt);
219+
j++;
220+
}
221+
}
222+
comments = grouped;
223+
}
224+
188225
return comments.Count > 0 ? new Space(ws, comments) : Build(formatting, []);
189226
}
190227

@@ -212,3 +249,11 @@ public abstract class Comment(string text, string suffix, bool multiline)
212249
/// </summary>
213250
public sealed class TextComment(string text, string suffix, bool multiline)
214251
: Comment(text, suffix, multiline);
252+
253+
/// <summary>
254+
/// An XML documentation comment (///) block.
255+
/// The Text property contains the raw content after the initial "//",
256+
/// including continuation "///" prefixes on subsequent lines.
257+
/// </summary>
258+
public sealed class XmlDocComment(string text, string suffix, bool multiline)
259+
: Comment(text, suffix, multiline);

rewrite-csharp/src/main/java/org/openrewrite/csharp/CSharpVisitor.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,46 @@
3535

3636
public class CSharpVisitor<P> extends JavaVisitor<P>
3737
{
38+
protected CsDocCommentVisitor<P> csXmlDocVisitor;
39+
3840
@Override
3941
public boolean isAcceptable(SourceFile sourceFile, P p) {
4042
return sourceFile instanceof Cs;
4143
}
4244

45+
protected CsDocCommentVisitor<P> getCsDocCommentVisitor() {
46+
return new CsDocCommentVisitor<>(this);
47+
}
48+
49+
@Override
50+
public Space visitSpace(@Nullable Space space, Space.Location loc, P p) {
51+
if (space == Space.EMPTY || space == Space.SINGLE_SPACE || space == null) {
52+
return space;
53+
} else if (space.getComments().isEmpty()) {
54+
return space;
55+
}
56+
return space.withComments(ListUtils.map(space.getComments(), comment -> {
57+
if (comment instanceof CsDocCommentRawComment) {
58+
// Convert raw doc comment from RPC into structured tree, then visit
59+
CsDocComment.DocComment parsed = CsDocCommentParser.parse((CsDocCommentRawComment) comment);
60+
return visitCsDocCommentComment(parsed, p);
61+
} else if (comment instanceof CsDocComment.DocComment) {
62+
return visitCsDocCommentComment((CsDocComment.DocComment) comment, p);
63+
}
64+
return comment;
65+
}));
66+
}
67+
68+
private Comment visitCsDocCommentComment(CsDocComment.DocComment docComment, P p) {
69+
if (csXmlDocVisitor == null) {
70+
csXmlDocVisitor = getCsDocCommentVisitor();
71+
}
72+
Cursor previous = csXmlDocVisitor.getCursor();
73+
Comment c = (Comment) csXmlDocVisitor.visit(docComment, p, getCursor());
74+
csXmlDocVisitor.setCursor(previous);
75+
return c;
76+
}
77+
4378
public J visitCompilationUnit(Cs.CompilationUnit compilationUnit, P p) {
4479
compilationUnit = compilationUnit.withPrefix(visitSpace(compilationUnit.getPrefix(), Space.Location.COMPILATION_UNIT_PREFIX, p));
4580
compilationUnit = compilationUnit.withMarkers(visitMarkers(compilationUnit.getMarkers(), p));

0 commit comments

Comments
 (0)