Skip to content

Commit 4bcccae

Browse files
authored
Merge pull request #2258 from bjornhellander/Issue2253_CorrectSa1623DiagnosticAndCodeFix
Corrected analyzer for SA1623 and SA1624 to report the correct diagnostic
2 parents 669518f + af6741e commit 4bcccae

3 files changed

Lines changed: 99 additions & 105 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1623UnitTests.cs

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -177,47 +177,54 @@ public int Property
177177
}
178178

179179
/// <summary>
180-
/// Verifies that an incomplete summary tag (with only get or set, when get and set are needed) will be fixed
181-
/// correctly. Regression test for #2098.
180+
/// Verifies that an incorrect summary tag with a known prefix will be fixed correctly.
182181
/// </summary>
182+
/// <param name="accessibility">The accessibility of the property.</param>
183+
/// <param name="type">The type to use for the property.</param>
184+
/// <param name="accessors">The accessors for the property.</param>
185+
/// <param name="summaryPrefix">The summary prefix used in the test code.</param>
186+
/// <param name="expectedArgument">The expected argument for the diagnostic message.</param>
183187
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
184-
[Fact]
185-
public async Task IncompleteSummaryTagShouldBeFixedCorrectlyAsync()
188+
[Theory]
189+
[InlineData("public", "int", "{ get; set; }", "Gets", "Gets or sets")] // Regression test for #2098
190+
[InlineData("public", "int", "{ get; set; }", "Sets", "Gets or sets")] // Regression test for #2098
191+
[InlineData("public", "int", "{ get; }", "Sets", "Gets")] // Regression test for #2253
192+
[InlineData("public", "int", "{ get; }", "Gets or sets", "Gets")] // Regression test for #2253
193+
[InlineData("public", "int", "=> 0;", "Sets", "Gets")] // Regression test for #2253
194+
[InlineData("public", "int", "=> 0;", "Gets or sets", "Gets")] // Regression test for #2253
195+
[InlineData("public", "bool", "=> false;", "Gets or sets a value indicating whether", "Gets a value indicating whether")] // Regression test for #2253
196+
[InlineData("protected", "int", "=> 0;", "Gets or sets", "Gets")] // Regression test for #2253
197+
[InlineData("protected internal", "int", "=> 0;", "Gets or sets", "Gets")] // Regression test for #2253
198+
[InlineData("internal", "int", "=> 0;", "Gets or sets", "Gets")] // Regression test for #2253
199+
[InlineData("public", "int", "{ set {} }", "Gets", "Sets")] // Regression test for #2253
200+
[InlineData("public", "int", "{ set {} }", "Gets or sets", "Sets")] // Regression test for #2253
201+
[InlineData("public", "int", "{ get; private set; }", "Sets", "Gets")] // Regression test for #2253
202+
[InlineData("public", "int", "{ private get; set; }", "Gets", "Sets")] // Regression test for #2253
203+
public async Task IncorrectSummaryTagWithKnownPrefixShouldBeFixedCorrectlyAsync(string accessibility, string type, string accessors, string summaryPrefix, string expectedArgument)
186204
{
187-
var testCode = @"
205+
var testCode = $@"
188206
public class TestClass
189-
{
190-
/// <summary>
191-
/// Gets the first test value.
192-
/// </summary>
193-
public int TestProperty1 { get; set; }
194-
207+
{{
195208
/// <summary>
196-
/// Sets the seconds test value.
209+
/// {summaryPrefix} the value.
197210
/// </summary>
198-
public int TestProperty2 { get; set; }
199-
}
211+
{accessibility} {type} TestProperty {accessors}
212+
}}
200213
";
201214

202-
var fixedTestCode = @"
215+
var fixedTestCode = $@"
203216
public class TestClass
204-
{
205-
/// <summary>
206-
/// Gets or sets the first test value.
207-
/// </summary>
208-
public int TestProperty1 { get; set; }
209-
217+
{{
210218
/// <summary>
211-
/// Gets or sets the seconds test value.
219+
/// {expectedArgument} the value.
212220
/// </summary>
213-
public int TestProperty2 { get; set; }
214-
}
221+
{accessibility} {type} TestProperty {accessors}
222+
}}
215223
";
216224

217225
DiagnosticResult[] expected =
218226
{
219-
this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(7, 16).WithArguments("Gets or sets"),
220-
this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(12, 16).WithArguments("Gets or sets"),
227+
this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(7, 7 + accessibility.Length + type.Length).WithArguments(expectedArgument),
221228
};
222229

223230
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1624UnitTests.cs

Lines changed: 1 addition & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public class TestClass
7878
}
7979

8080
/// <summary>
81-
/// Verifies that documentation that starts with the proper text for multiple accessors will not produce this
81+
/// Verifies that documentation that starts with the proper text for a lone getter will not produce this
8282
/// diagnostic when the property has an expression body.
8383
/// </summary>
8484
/// <param name="accessibility">The accessibility of the property.</param>
@@ -107,52 +107,6 @@ public class TestClass
107107
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
108108
}
109109

110-
/// <summary>
111-
/// Verifies that documentation that starts with the proper text for multiple accessors will produce a diagnostic for expression body properties.
112-
/// </summary>
113-
/// <param name="accessibility">The accessibility of the property.</param>
114-
/// <param name="type">The type to use for the property.</param>
115-
/// <param name="summaryPrefix">The prefix to use in the summary text.</param>
116-
/// <param name="expectedArgument1">The first expected argument for the diagnostic.</param>
117-
/// <param name="expectedArgument2">The second expected argument for the diagnostic.</param>
118-
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
119-
[Theory(DisplayName = "ExpressionBody Gets&Sets")]
120-
[InlineData("public", "int", "Gets or sets", "get", "Gets")]
121-
[InlineData("public", "bool", "Gets or sets a value indicating whether", "get", "Gets a value indicating whether")]
122-
[InlineData("protected", "int", "Gets or sets", "get", "Gets")]
123-
[InlineData("protected internal", "int", "Gets or sets", "get", "Gets")]
124-
[InlineData("internal", "int", "Gets or sets", "get", "Gets")]
125-
public async Task VerifyThatInvalidDocumentationWillReportDiagnosticForExpressionBodyAsync(string accessibility, string type, string summaryPrefix, string expectedArgument1, string expectedArgument2)
126-
{
127-
var testCode = $@"
128-
public class TestClass
129-
{{
130-
/// <summary>
131-
/// {summaryPrefix} the test property.
132-
/// </summary>
133-
{accessibility} {type} TestProperty =>
134-
default({type});
135-
}}
136-
";
137-
138-
var fixedTestCode = $@"
139-
public class TestClass
140-
{{
141-
/// <summary>
142-
/// {expectedArgument2} the test property.
143-
/// </summary>
144-
{accessibility} {type} TestProperty =>
145-
default({type});
146-
}}
147-
";
148-
149-
var expected = this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1624Descriptor).WithLocation(7, 7 + accessibility.Length + type.Length).WithArguments(expectedArgument1, expectedArgument2);
150-
151-
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
152-
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
153-
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
154-
}
155-
156110
/// <summary>
157111
/// Verify that no diagnostic will be reported when a public and a private accessor are present within a property that is defined in a contained class of a private class.
158112
/// </summary>

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/PropertySummaryDocumentationAnalyzer.cs

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -130,25 +130,26 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
130130
var textElement = summaryElement.Content.FirstOrDefault() as XmlTextSyntax;
131131
var text = textElement == null ? string.Empty : XmlCommentHelper.GetText(textElement, true).TrimStart();
132132

133-
bool startsWithGetOrSet = text.StartsWith(startingTextGetsOrSets, StringComparison.OrdinalIgnoreCase);
133+
bool prefixIsGetsOrSets = text.StartsWith(startingTextGetsOrSets, StringComparison.OrdinalIgnoreCase);
134+
bool prefixIsGets = !prefixIsGetsOrSets && text.StartsWith(startingTextGets, StringComparison.OrdinalIgnoreCase);
135+
bool prefixIsSets = text.StartsWith(startingTextSets, StringComparison.OrdinalIgnoreCase);
136+
134137
bool getterVisible, setterVisible;
135138
if (getter != null && setter != null)
136139
{
137140
if (!getter.Modifiers.Any() && !setter.Modifiers.Any())
138141
{
139-
// Case 1: The getter and setter have the same declared accessibility
142+
// The getter and setter have the same declared accessibility
140143
getterVisible = true;
141144
setterVisible = true;
142145
}
143146
else if (getter.Modifiers.Any(SyntaxKind.PrivateKeyword))
144147
{
145-
// Case 3
146148
getterVisible = false;
147149
setterVisible = true;
148150
}
149151
else if (setter.Modifiers.Any(SyntaxKind.PrivateKeyword))
150152
{
151-
// Case 3
152153
getterVisible = true;
153154
setterVisible = false;
154155
}
@@ -160,7 +161,7 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
160161
|| propertyAccessibility == Accessibility.Private;
161162
if (propertyOnlyInternal)
162163
{
163-
// Case 2: Property only internal and no accessor is explicitly private
164+
// Property only internal and no accessor is explicitly private
164165
getterVisible = true;
165166
setterVisible = true;
166167
}
@@ -174,7 +175,6 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
174175
case Accessibility.Public:
175176
case Accessibility.ProtectedOrInternal:
176177
case Accessibility.Protected:
177-
// Case 4
178178
getterVisible = true;
179179
break;
180180

@@ -192,7 +192,6 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
192192
case Accessibility.Public:
193193
case Accessibility.ProtectedOrInternal:
194194
case Accessibility.Protected:
195-
// Case 4
196195
setterVisible = true;
197196
break;
198197

@@ -225,51 +224,85 @@ private static void AnalyzeSummaryElement(SyntaxNodeAnalysisContext context, Xml
225224
{
226225
if (setterVisible)
227226
{
228-
if (!startsWithGetOrSet)
227+
// Both getter and setter are visible.
228+
if (!prefixIsGetsOrSets)
229229
{
230-
diagnosticProperties.Add(ExpectedTextKey, startingTextGetsOrSets);
231-
232-
if (text.StartsWith(startingTextGets, StringComparison.OrdinalIgnoreCase))
230+
ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGetsOrSets, unexpectedStartingText1: startingTextGets, unexpectedStartingText2: startingTextSets);
231+
}
232+
}
233+
else if (setter != null)
234+
{
235+
// Both getter and setter exist, but only getter is visible.
236+
if (!prefixIsGets)
237+
{
238+
if (prefixIsGetsOrSets)
233239
{
234-
diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, startingTextGets.Length));
240+
ReportSA1624(context, diagnosticLocation, diagnosticProperties, accessor: "get", expectedStartingText: startingTextGets, startingTextToRemove: startingTextGetsOrSets);
235241
}
236-
else if (text.StartsWith(startingTextSets, StringComparison.OrdinalIgnoreCase))
242+
else
237243
{
238-
diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, startingTextSets.Length));
244+
ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGets, unexpectedStartingText1: startingTextSets);
239245
}
240-
241-
context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGetsOrSets));
242246
}
243247
}
244248
else
245249
{
246-
if (startsWithGetOrSet)
250+
// Getter exists and is visible. Setter does not exist.
251+
if (!prefixIsGets)
247252
{
248-
diagnosticProperties.Add(ExpectedTextKey, startingTextGets);
249-
diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets);
250-
context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "get", startingTextGets));
251-
}
252-
else if (!text.StartsWith(startingTextGets, StringComparison.OrdinalIgnoreCase))
253-
{
254-
diagnosticProperties.Add(ExpectedTextKey, startingTextGets);
255-
context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextGets));
253+
ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextGets, unexpectedStartingText1: startingTextSets, unexpectedStartingText2: startingTextGetsOrSets);
256254
}
257255
}
258256
}
259257
else if (setterVisible)
260258
{
261-
if (startsWithGetOrSet)
259+
if (getter != null)
262260
{
263-
diagnosticProperties.Add(ExpectedTextKey, startingTextSets);
264-
diagnosticProperties.Add(TextToRemoveKey, startingTextGetsOrSets);
265-
context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), "set", startingTextSets));
261+
// Both getter and setter exist, but only setter is visible.
262+
if (!prefixIsSets)
263+
{
264+
if (prefixIsGetsOrSets)
265+
{
266+
ReportSA1624(context, diagnosticLocation, diagnosticProperties, accessor: "set", expectedStartingText: startingTextSets, startingTextToRemove: startingTextGetsOrSets);
267+
}
268+
else
269+
{
270+
ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextSets, unexpectedStartingText1: startingTextGets);
271+
}
272+
}
266273
}
267-
else if (!text.StartsWith(startingTextSets, StringComparison.OrdinalIgnoreCase))
274+
else
268275
{
269-
diagnosticProperties.Add(ExpectedTextKey, startingTextSets);
270-
context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), startingTextSets));
276+
// Setter exists and is visible. Getter does not exist.
277+
if (!prefixIsSets)
278+
{
279+
ReportSA1623(context, diagnosticLocation, diagnosticProperties, text, expectedStartingText: startingTextSets, unexpectedStartingText1: startingTextGetsOrSets, unexpectedStartingText2: startingTextGets);
280+
}
271281
}
272282
}
273283
}
284+
285+
private static void ReportSA1623(SyntaxNodeAnalysisContext context, Location diagnosticLocation, ImmutableDictionary<string, string>.Builder diagnosticProperties, string text, string expectedStartingText, string unexpectedStartingText1, string unexpectedStartingText2 = null)
286+
{
287+
diagnosticProperties.Add(ExpectedTextKey, expectedStartingText);
288+
289+
if (text.StartsWith(unexpectedStartingText1, StringComparison.OrdinalIgnoreCase))
290+
{
291+
diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, unexpectedStartingText1.Length));
292+
}
293+
else if ((unexpectedStartingText2 != null) && text.StartsWith(unexpectedStartingText2, StringComparison.OrdinalIgnoreCase))
294+
{
295+
diagnosticProperties.Add(TextToRemoveKey, text.Substring(0, unexpectedStartingText2.Length));
296+
}
297+
298+
context.ReportDiagnostic(Diagnostic.Create(SA1623Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), expectedStartingText));
299+
}
300+
301+
private static void ReportSA1624(SyntaxNodeAnalysisContext context, Location diagnosticLocation, ImmutableDictionary<string, string>.Builder diagnosticProperties, string accessor, string expectedStartingText, string startingTextToRemove)
302+
{
303+
diagnosticProperties.Add(ExpectedTextKey, expectedStartingText);
304+
diagnosticProperties.Add(TextToRemoveKey, startingTextToRemove);
305+
context.ReportDiagnostic(Diagnostic.Create(SA1624Descriptor, diagnosticLocation, diagnosticProperties.ToImmutable(), accessor, expectedStartingText));
306+
}
274307
}
275308
}

0 commit comments

Comments
 (0)