Skip to content

Commit ce42508

Browse files
committed
Improve SA1642 CF
1 parent 98b446f commit ce42508

2 files changed

Lines changed: 264 additions & 2 deletions

File tree

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

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,102 @@ public async Task TestStaticConstructorMissingDocumentationGenericAsync(string t
219219
await this.TestConstructorMissingDocumentationAsync(typeKind, "static", StaticConstructorStandardText, $" {typeKind}.", true).ConfigureAwait(false);
220220
}
221221

222+
[Theory]
223+
[InlineData("class")]
224+
[InlineData("struct")]
225+
public async Task TestNonPrivateConstructorSimpleDocumentationAsync(string typeKind)
226+
{
227+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "public", NonPrivateConstructorStandardText, $" {typeKind}", false).ConfigureAwait(false);
228+
}
229+
230+
[Theory]
231+
[InlineData("class")]
232+
[InlineData("struct")]
233+
public async Task TestNonPrivateConstructorSimpleDocumentationGenericAsync(string typeKind)
234+
{
235+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "public", NonPrivateConstructorStandardText, $" {typeKind}", true).ConfigureAwait(false);
236+
}
237+
238+
[Theory]
239+
[InlineData("class")]
240+
[InlineData("struct")]
241+
public async Task TestPrivateConstructorSimpleDocumentationAsync(string typeKind)
242+
{
243+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "private", NonPrivateConstructorStandardText, $" {typeKind}", false).ConfigureAwait(false);
244+
}
245+
246+
[Theory]
247+
[InlineData("class")]
248+
[InlineData("struct")]
249+
public async Task TestPrivateConstructorSimpleDocumentationGenericAsync(string typeKind)
250+
{
251+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "private", NonPrivateConstructorStandardText, $" {typeKind}", true).ConfigureAwait(false);
252+
}
253+
254+
[Theory]
255+
[InlineData("class")]
256+
[InlineData("struct")]
257+
public async Task TestStaticConstructorSimpleDocumentationAsync(string typeKind)
258+
{
259+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "static", StaticConstructorStandardText, $" {typeKind}.", false).ConfigureAwait(false);
260+
}
261+
262+
[Theory]
263+
[InlineData("class")]
264+
[InlineData("struct")]
265+
public async Task TestStaticConstructorSimpleDocumentationGenericAsync(string typeKind)
266+
{
267+
await this.TestConstructorSimpleDocumentationAsync(typeKind, "static", StaticConstructorStandardText, $" {typeKind}.", true).ConfigureAwait(false);
268+
}
269+
270+
[Theory]
271+
[InlineData("class")]
272+
[InlineData("struct")]
273+
public async Task TestNonPrivateConstructorSimpleDocumentationWrongTypeNameAsync(string typeKind)
274+
{
275+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "public", NonPrivateConstructorStandardText, $" {typeKind}", false).ConfigureAwait(false);
276+
}
277+
278+
[Theory]
279+
[InlineData("class")]
280+
[InlineData("struct")]
281+
public async Task TestNonPrivateConstructorSimpleDocumentationGenericWrongTypeNameAsync(string typeKind)
282+
{
283+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "public", NonPrivateConstructorStandardText, $" {typeKind}", true).ConfigureAwait(false);
284+
}
285+
286+
[Theory]
287+
[InlineData("class")]
288+
[InlineData("struct")]
289+
public async Task TestPrivateConstructorSimpleDocumentationWrongTypeNameAsync(string typeKind)
290+
{
291+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "private", NonPrivateConstructorStandardText, $" {typeKind}", false).ConfigureAwait(false);
292+
}
293+
294+
[Theory]
295+
[InlineData("class")]
296+
[InlineData("struct")]
297+
public async Task TestPrivateConstructorSimpleDocumentationGenericWrongTypeNameAsync(string typeKind)
298+
{
299+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "private", NonPrivateConstructorStandardText, $" {typeKind}", true).ConfigureAwait(false);
300+
}
301+
302+
[Theory]
303+
[InlineData("class")]
304+
[InlineData("struct")]
305+
public async Task TestStaticConstructorSimpleDocumentationWrongTypeNameAsync(string typeKind)
306+
{
307+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "static", StaticConstructorStandardText, $" {typeKind}.", false).ConfigureAwait(false);
308+
}
309+
310+
[Theory]
311+
[InlineData("class")]
312+
[InlineData("struct")]
313+
public async Task TestStaticConstructorSimpleDocumentationGenericWrongTypeNameAsync(string typeKind)
314+
{
315+
await this.TestConstructorSimpleDocumentationWrongTypeNameAsync(typeKind, "static", StaticConstructorStandardText, $" {typeKind}.", true).ConfigureAwait(false);
316+
}
317+
222318
/// <summary>
223319
/// This is a regression test for DotNetAnalyzers/StyleCopAnalyzers#676 "SA1642 misfires on nested structs,
224320
/// requiring text describing the outer type"
@@ -470,5 +566,95 @@ await this.VerifyCSharpDiagnosticAsync(
470566
fixedCode = string.Format(fixedCode, typeKind, generic ? "<T1, T2>" : string.Empty, generic ? "{T1, T2}" : string.Empty, part1, part2, part3, modifiers, arguments);
471567
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
472568
}
569+
570+
private async Task TestConstructorSimpleDocumentationAsync(string typeKind, string modifiers, string part1, string part2, bool generic)
571+
{
572+
string part3 = part2.EndsWith(".") ? string.Empty : ".";
573+
574+
var testCode = @"namespace FooNamespace
575+
{{
576+
public {0} Foo{1}
577+
{{
578+
/// <summary>
579+
/// {3}Foo{4}{5}
580+
/// </summary>
581+
{6}
582+
Foo({7})
583+
{{
584+
585+
}}
586+
}}
587+
}}";
588+
string arguments = typeKind == "struct" && modifiers != "static" ? "int argument" : null;
589+
testCode = string.Format(testCode, typeKind, generic ? "<T1, T2>" : string.Empty, generic ? "{T1, T2}" : string.Empty, part1, part2, part3, modifiers, arguments);
590+
591+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, 13);
592+
593+
await this.VerifyCSharpDiagnosticAsync(
594+
testCode,
595+
expected, CancellationToken.None).ConfigureAwait(false);
596+
597+
var fixedCode = @"namespace FooNamespace
598+
{{
599+
public {0} Foo{1}
600+
{{
601+
/// <summary>
602+
/// {3}<see cref=""Foo{2}""/>{4}{5}
603+
/// </summary>
604+
{6}
605+
Foo({7})
606+
{{
607+
608+
}}
609+
}}
610+
}}";
611+
fixedCode = string.Format(fixedCode, typeKind, generic ? "<T1, T2>" : string.Empty, generic ? "{T1, T2}" : string.Empty, part1, part2, part3, modifiers, arguments);
612+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
613+
}
614+
615+
private async Task TestConstructorSimpleDocumentationWrongTypeNameAsync(string typeKind, string modifiers, string part1, string part2, bool generic)
616+
{
617+
string part3 = part2.EndsWith(".") ? string.Empty : ".";
618+
619+
var testCode = @"namespace FooNamespace
620+
{{
621+
public {0} Foo{1}
622+
{{
623+
/// <summary>
624+
/// {3}Bar{4}{5}
625+
/// </summary>
626+
{6}
627+
Foo({7})
628+
{{
629+
630+
}}
631+
}}
632+
}}";
633+
string arguments = typeKind == "struct" && modifiers != "static" ? "int argument" : null;
634+
testCode = string.Format(testCode, typeKind, generic ? "<T1, T2>" : string.Empty, generic ? "{T1, T2}" : string.Empty, part1, part2, part3, modifiers, arguments);
635+
636+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, 13);
637+
638+
await this.VerifyCSharpDiagnosticAsync(
639+
testCode,
640+
expected, CancellationToken.None).ConfigureAwait(false);
641+
642+
var fixedCode = @"namespace FooNamespace
643+
{{
644+
public {0} Foo{1}
645+
{{
646+
/// <summary>
647+
/// {3}<see cref=""Foo{2}""/>{4}{5}
648+
/// </summary>
649+
{6}
650+
Foo({7})
651+
{{
652+
653+
}}
654+
}}
655+
}}";
656+
fixedCode = string.Format(fixedCode, typeKind, generic ? "<T1, T2>" : string.Empty, generic ? "{T1, T2}" : string.Empty, part1, part2, part3, modifiers, arguments);
657+
await this.VerifyCSharpFixAsync(testCode, fixedCode).ConfigureAwait(false);
658+
}
473659
}
474660
}

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

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ namespace StyleCop.Analyzers.DocumentationRules
77
using System.Collections.Immutable;
88
using System.Composition;
99
using System.Linq;
10+
using System.Text.RegularExpressions;
1011
using System.Threading.Tasks;
1112
using Helpers;
1213
using Microsoft.CodeAnalysis;
@@ -119,9 +120,13 @@ private static Task<Document> GetTransformedDocumentAsync(Document document, Syn
119120
}
120121

121122
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
122-
var list = BuildStandardText(typeDeclaration.Identifier, typeParameterList, newLineText, standardText[0], standardText[1]);
123123

124-
var newContent = node.Content.InsertRange(0, list);
124+
string trailingString = string.Empty;
125+
126+
var newContent = RemoveMalformattedStandardText(node.Content, typeDeclaration.Identifier, standardText[0], standardText[1], ref trailingString);
127+
128+
var list = BuildStandardText(typeDeclaration.Identifier, typeParameterList, newLineText, standardText[0], standardText[1] + trailingString);
129+
newContent = newContent.InsertRange(0, list);
125130
var newNode = node.WithContent(newContent).AdjustDocumentationCommentNewLineTrivia();
126131

127132
var newRoot = root.ReplaceNode(node, newNode);
@@ -131,6 +136,77 @@ private static Task<Document> GetTransformedDocumentAsync(Document document, Syn
131136
return Task.FromResult(newDocument);
132137
}
133138

139+
private static SyntaxList<XmlNodeSyntax> RemoveMalformattedStandardText(SyntaxList<XmlNodeSyntax> content, SyntaxToken identifier, string preText, string postText, ref string trailingString)
140+
{
141+
var regex = new Regex(@"^\s*" + Regex.Escape(preText) + "[^ ]+" + Regex.Escape(postText));
142+
var item = content.OfType<XmlTextSyntax>().FirstOrDefault();
143+
144+
if (item == null)
145+
{
146+
return content;
147+
}
148+
149+
int index = -1;
150+
foreach (var token in item.TextTokens)
151+
{
152+
index++;
153+
154+
if (token.IsKind(SyntaxKind.XmlTextLiteralNewLineToken))
155+
{
156+
continue;
157+
}
158+
else if (token.IsKind(SyntaxKind.XmlTextLiteralToken))
159+
{
160+
string value = token.ValueText.Trim(null);
161+
162+
Match match = regex.Match(value);
163+
164+
if (!match.Success)
165+
{
166+
return content;
167+
}
168+
else if (match.Length == value.Length)
169+
{
170+
// Remove the token
171+
var tokens = item.TextTokens;
172+
173+
while (index >= 0)
174+
{
175+
tokens = tokens.RemoveAt(0);
176+
index--;
177+
}
178+
179+
var newContent = item.WithTextTokens(tokens);
180+
181+
return content.Replace(item, newContent);
182+
}
183+
else
184+
{
185+
// Remove the tokens before
186+
var tokens = item.TextTokens;
187+
188+
while (index >= 0)
189+
{
190+
tokens = tokens.RemoveAt(0);
191+
index--;
192+
}
193+
194+
trailingString = value.Substring(match.Length);
195+
196+
var newContent = item.WithTextTokens(tokens);
197+
198+
return content.Replace(item, newContent);
199+
}
200+
}
201+
else
202+
{
203+
return content;
204+
}
205+
}
206+
207+
return content;
208+
}
209+
134210
private static SyntaxList<XmlNodeSyntax> BuildStandardText(SyntaxToken identifier, TypeParameterListSyntax typeParameters, string newLineText, string preText, string postText)
135211
{
136212
TypeSyntax identifierName;

0 commit comments

Comments
 (0)