Skip to content

Commit ac10b44

Browse files
committed
Merge pull request #2293 from vweijsters/improve-constructor-codefix
Improves SA1642/SA1643 codefix when working with default Visual Studio generated documentation headers
2 parents 67323ee + 67c0e57 commit ac10b44

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1642SA1643CodeFixProvider.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ private static Task<Document> GetTransformedDocumentAsync(Document document, Syn
140140

141141
var list = BuildStandardText(typeDeclaration.Identifier, typeParameterList, newLineText, standardText[0], standardText[1] + trailingString);
142142
newContent = newContent.InsertRange(0, list);
143+
144+
newContent = RemoveTrailingEmptyLines(newContent);
145+
143146
var newNode = node.WithContent(newContent).AdjustDocumentationCommentNewLineTrivia();
144147

145148
var newRoot = root.ReplaceNode(node, newNode);
@@ -284,5 +287,39 @@ private static TypeArgumentListSyntax ParameterToArgumentListSyntax(TypeParamete
284287

285288
return SyntaxFactory.TypeArgumentList(list);
286289
}
290+
291+
private static SyntaxList<XmlNodeSyntax> RemoveTrailingEmptyLines(SyntaxList<XmlNodeSyntax> content)
292+
{
293+
var xmlText = content[content.Count - 1] as XmlTextSyntax;
294+
if (xmlText == null)
295+
{
296+
return content;
297+
}
298+
299+
// skip the last token, as it contains the documentation comment for the closing tag, which needs to remain.
300+
var firstEmptyToken = -1;
301+
for (var j = xmlText.TextTokens.Count - 2; j >= 0; j--)
302+
{
303+
var textToken = xmlText.TextTokens[j];
304+
305+
if (textToken.IsXmlWhitespace())
306+
{
307+
firstEmptyToken = j;
308+
}
309+
else if (textToken.IsKind(SyntaxKind.XmlTextLiteralToken) && !string.IsNullOrWhiteSpace(textToken.Text))
310+
{
311+
break;
312+
}
313+
}
314+
315+
if (firstEmptyToken > -1)
316+
{
317+
var newContent = SyntaxFactory.List(content.Take(firstEmptyToken));
318+
newContent.Add(newContent.Last());
319+
return newContent;
320+
}
321+
322+
return content;
323+
}
287324
}
288325
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1642UnitTests.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,76 @@ await this.TestConstructorCorrectDocumentationSimpleAsync(
887887
false).ConfigureAwait(false);
888888
}
889889

890+
/// <summary>
891+
/// Verify that the codefix will work properly with Visual Studio generated documentation headers.
892+
/// </summary>
893+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
894+
[Fact]
895+
public async Task TestWithDefaultVisualStudioGenerationDocumentationAsync()
896+
{
897+
var testCode = @"
898+
public class TestClass
899+
{
900+
/// <summary>
901+
///
902+
/// </summary>
903+
public TestClass() { }
904+
}
905+
";
906+
907+
var fixedCode = @"
908+
public class TestClass
909+
{
910+
/// <summary>
911+
/// Initializes a new instance of the <see cref=""TestClass""/> class.
912+
/// </summary>
913+
public TestClass() { }
914+
}
915+
";
916+
917+
var expected = this.CSharpDiagnostic().WithLocation(4, 9);
918+
919+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
920+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
921+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false);
922+
}
923+
924+
/// <summary>
925+
/// Verify that the codefix will work properly when there are multiple empty lines.
926+
/// </summary>
927+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
928+
[Fact]
929+
public async Task TestWithMultipleEmptyLinesAsync()
930+
{
931+
var testCode = @"
932+
public class TestClass
933+
{
934+
/// <summary>
935+
///
936+
///
937+
///
938+
/// </summary>
939+
public TestClass() { }
940+
}
941+
";
942+
943+
var fixedCode = @"
944+
public class TestClass
945+
{
946+
/// <summary>
947+
/// Initializes a new instance of the <see cref=""TestClass""/> class.
948+
/// </summary>
949+
public TestClass() { }
950+
}
951+
";
952+
953+
var expected = this.CSharpDiagnostic().WithLocation(4, 9);
954+
955+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
956+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
957+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false);
958+
}
959+
890960
protected override Project ApplyCompilationOptions(Project project)
891961
{
892962
var resolver = new TestXmlReferenceResolver();

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1643UnitTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,74 @@ public class TestClass
152152
Assert.Empty(offeredFixes);
153153
}
154154

155+
/// <summary>
156+
/// Verify that the codefix will work properly with Visual Studio generated documentation headers.
157+
/// </summary>
158+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
159+
[Fact]
160+
public async Task TestWithDefaultVisualStudioGenerationDocumentationAsync()
161+
{
162+
var testCode = @"
163+
public class TestClass
164+
{
165+
/// <summary>
166+
///
167+
/// </summary>
168+
~TestClass() { }
169+
}
170+
";
171+
172+
var fixedCode = @"
173+
public class TestClass
174+
{
175+
/// <summary>
176+
/// Finalizes an instance of the <see cref=""TestClass""/> class.
177+
/// </summary>
178+
~TestClass() { }
179+
}
180+
";
181+
182+
var expected = this.CSharpDiagnostic().WithLocation(4, 9);
183+
184+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
185+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
186+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false);
187+
}
188+
189+
/// <summary>
190+
/// Verify that the codefix will work properly when there are multiple empty lines.
191+
/// </summary>
192+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
193+
[Fact]
194+
public async Task TestWithMultipleEmptyLinesAsync()
195+
{
196+
var testCode = @"
197+
public class TestClass
198+
{
199+
/// <summary>
200+
///
201+
/// </summary>
202+
~TestClass() { }
203+
}
204+
";
205+
206+
var fixedCode = @"
207+
public class TestClass
208+
{
209+
/// <summary>
210+
/// Finalizes an instance of the <see cref=""TestClass""/> class.
211+
/// </summary>
212+
~TestClass() { }
213+
}
214+
";
215+
216+
var expected = this.CSharpDiagnostic().WithLocation(4, 9);
217+
218+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
219+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
220+
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false);
221+
}
222+
155223
protected override Project ApplyCompilationOptions(Project project)
156224
{
157225
var resolver = new TestXmlReferenceResolver();

0 commit comments

Comments
 (0)