Skip to content

Commit 83991a0

Browse files
Don't trim leading whitespace from trivia - fixes #711
1 parent 0c39437 commit 83991a0

5 files changed

Lines changed: 106 additions & 65 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1515
* All handlers from multi-line handles syntax now converted [#701](https://github.com/icsharpcode/CodeConverter/issues/701)
1616
* Implicilty typed inherited events no longer create extra delegates [#700](https://github.com/icsharpcode/CodeConverter/issues/700)
1717
* Keep optional parameters for parameterized properties [#642](https://github.com/icsharpcode/CodeConverter/issues/642)
18+
* Maintain leading whitespace in comments [#711](https://github.com/icsharpcode/CodeConverter/issues/711)
1819

1920
### C# -> VB
2021

CodeConverter/Util/SyntaxNodeExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,9 @@ private static IEnumerable<SyntaxTrivia> ConvertVBTrivia(SyntaxTrivia t)
380380
if (t.IsKind(VBSyntaxKind.DocumentationCommentTrivia)) {
381381
var previousWhitespace = t.GetPreviousTrivia(t.SyntaxTree, CancellationToken.None).ToString().Trim('\r', '\n');
382382
var commentTextLines = t.GetCommentText().Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
383-
var outputCommentText = "/// " + String.Join($"\r\n{previousWhitespace}/// ", commentTextLines) + Environment.NewLine;
383+
var outputCommentText = "/// " + String.Join($"\r\n{previousWhitespace}/// ", commentTextLines);
384384
yield return SyntaxFactory.SyntaxTrivia(CSSyntaxKind.SingleLineCommentTrivia, outputCommentText); //It's always single line...even when it has multiple lines
385+
yield return _endOfLine;
385386
yield break;
386387
}
387388

CodeConverter/Util/SyntaxTriviaExtensions.cs

Lines changed: 11 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using CS = Microsoft.CodeAnalysis.CSharp;
88
using VBasic = Microsoft.CodeAnalysis.VisualBasic;
99
using CSSyntax = Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using System.Text.RegularExpressions;
1011

1112
namespace ICSharpCode.CodeConverter.Util
1213
{
@@ -68,58 +69,30 @@ public static string GetCommentText(this SyntaxTrivia trivia)
6869
commentText = commentText.Substring(2);
6970
}
7071

71-
commentText = commentText.Trim();
7272

7373
var newLine = Environment.NewLine;
7474
var lines = commentText.Split(new[] { newLine }, StringSplitOptions.None);
7575
foreach (var line in lines) {
76-
var trimmedLine = line.Trim();
7776

78-
// Note: we trim leading '*' characters in multi-line comments.
79-
// If the '*' was intentional, sorry, it's gone.
80-
if (trimmedLine.StartsWith("*")) {
81-
trimmedLine = trimmedLine.TrimStart('*');
82-
trimmedLine = trimmedLine.TrimStart(null);
83-
}
84-
85-
textBuilder.AppendLine(trimmedLine);
77+
textBuilder.AppendLine(line);
8678
}
8779

88-
// remove last line break
89-
textBuilder.Remove(textBuilder.Length - newLine.Length, newLine.Length);
90-
91-
return textBuilder.ToString();
80+
// remove trailing line breaks
81+
return textBuilder.ToString().TrimEnd();
9282
} else if (trivia.IsKind(VBasic.SyntaxKind.DocumentationCommentTrivia) || CS.CSharpExtensions.Kind(trivia) == CS.SyntaxKind.SingleLineDocumentationCommentTrivia) {
9383
var textBuilder = new StringBuilder();
9484

95-
if (commentText.EndsWith("*/")) {
96-
commentText = commentText.TrimEnd('\'');
97-
}
98-
99-
if (commentText.StartsWith("'''")) {
100-
commentText = commentText.TrimStart('\'');
101-
}
102-
103-
commentText = commentText.Trim();
104-
10585
var lines = commentText.Replace("\r\n", "\n").Split('\n');
10686
foreach (var line in lines) {
107-
var trimmedLine = line.Trim();
108-
109-
// Note: we trim leading ' characters in multi-line comments.
110-
// If the ' was intentional, sorry, it's gone.
111-
if (trimmedLine.StartsWith("'")) {
112-
trimmedLine = trimmedLine.TrimStart('\'');
113-
trimmedLine = trimmedLine.TrimStart(null);
114-
}
115-
if (trimmedLine.StartsWith("/")) {
116-
trimmedLine = trimmedLine.TrimStart('/');
117-
trimmedLine = trimmedLine.TrimStart(null);
118-
}
119-
120-
textBuilder.AppendLine(trimmedLine);
87+
// Single line comment marker is part of the comment text
88+
string trimmed = Regex.Replace(line, @"^(\ *''')?\ ?", "");
89+
// Generated doc comments are indented by one space in VB, this is not idiomatic in C#
90+
// If someone has intentionally indented by a single space at the start for ascii art or something, this will unfortunately be lost.
91+
trimmed = Regex.Replace(trimmed, @"^\ ([^ ])", "$1");
92+
textBuilder.AppendLine(trimmed);
12193
}
12294

95+
// remove trailing line breaks
12396
return textBuilder.ToString().TrimEnd();
12497
}
12598

Tests/CSharp/MemberTests/MemberTests.cs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -296,32 +296,6 @@ public int TestMethod()
296296
}");
297297
}
298298

299-
[Fact]
300-
public async Task TestMethodXmlDocAsync()
301-
{
302-
await TestConversionVisualBasicToCSharpAsync(
303-
@"Class TestClass
304-
''' <summary>Xml doc</summary>
305-
Public Sub TestMethod(Of T As {Class, New}, T2 As Structure, T3)(<Out> ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3)
306-
argument = Nothing
307-
argument2 = Nothing
308-
argument3 = Nothing
309-
End Sub
310-
End Class", @"
311-
internal partial class TestClass
312-
{
313-
/// <summary>Xml doc</summary>
314-
public void TestMethod<T, T2, T3>(out T argument, ref T2 argument2, T3 argument3)
315-
where T : class, new()
316-
where T2 : struct
317-
{
318-
argument = null;
319-
argument2 = default;
320-
argument3 = default;
321-
}
322-
}");
323-
}
324-
325299
[Fact]
326300
public async Task TestMethodWithOutParameterAsync()
327301
{

Tests/CSharp/TriviaTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,98 @@ End Sub
146146
*/ /* TODO ERROR: Skipped EndIfDirectiveTrivia
147147
#End If
148148
*/
149+
}");
150+
}
151+
152+
[Fact]
153+
public async Task Issue771_DoNotTrimLineCommentsAsync()
154+
{
155+
await TestConversionVisualBasicToCSharpAsync(
156+
@"
157+
'>> Thomas 16.03.2021
158+
' bei BearbeitungsTyp = ""SP__unten""
159+
Public Class AClass
160+
End Class",
161+
@"
162+
// >> Thomas 16.03.2021
163+
// bei BearbeitungsTyp = ""SP__unten""
164+
165+
public partial class AClass
166+
{
167+
}");
168+
}
169+
170+
[Fact]
171+
public async Task Issue771_DoNotTrimBlockCommentsAsync()
172+
{
173+
await TestConversionVisualBasicToCSharpAsync(
174+
@"
175+
''' >> Thomas 16.03.2021
176+
''' bei BearbeitungsTyp = ""SP__unten""
177+
Public Class AClass
178+
End Class",
179+
@"
180+
/// >> Thomas 16.03.2021
181+
/// bei BearbeitungsTyp = ""SP__unten""
182+
183+
public partial class AClass
184+
{
185+
}");
186+
}
187+
188+
[Fact]
189+
public async Task TestMethodXmlDocAsync()
190+
{
191+
await TestConversionVisualBasicToCSharpAsync(
192+
@"Class TestClass
193+
''' <summary>Xml doc</summary>
194+
Public Sub TestMethod(Of T As {Class, New}, T2 As Structure, T3)(<Out> ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3)
195+
argument = Nothing
196+
argument2 = Nothing
197+
argument3 = Nothing
198+
End Sub
199+
End Class", @"
200+
internal partial class TestClass
201+
{
202+
/// <summary>Xml doc</summary>
203+
public void TestMethod<T, T2, T3>(out T argument, ref T2 argument2, T3 argument3)
204+
where T : class, new()
205+
where T2 : struct
206+
{
207+
argument = null;
208+
argument2 = default;
209+
argument3 = default;
210+
}
211+
}");
212+
}
213+
214+
[Fact]
215+
public async Task TestGeneratedMethodXmlDocAsync()
216+
{
217+
await TestConversionVisualBasicToCSharpAsync(
218+
@"Class TestClass
219+
'''<summary>
220+
''' Returns the cached ResourceManager instance used by this class.
221+
'''</summary>
222+
Public Sub TestMethod(Of T As {Class, New}, T2 As Structure, T3)(<Out> ByRef argument As T, ByRef argument2 As T2, ByVal argument3 As T3)
223+
argument = Nothing
224+
argument2 = Nothing
225+
argument3 = Nothing
226+
End Sub
227+
End Class", @"
228+
internal partial class TestClass
229+
{
230+
/// <summary>
231+
/// Returns the cached ResourceManager instance used by this class.
232+
/// </summary>
233+
public void TestMethod<T, T2, T3>(out T argument, ref T2 argument2, T3 argument3)
234+
where T : class, new()
235+
where T2 : struct
236+
{
237+
argument = null;
238+
argument2 = default;
239+
argument3 = default;
240+
}
149241
}");
150242
}
151243
}

0 commit comments

Comments
 (0)