Skip to content

Commit 4c1729f

Browse files
committed
Implemented SA1623 and SA1624 (incl. code fix and tests)
1 parent 6ede78d commit 4c1729f

14 files changed

Lines changed: 700 additions & 436 deletions
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
5+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
6+
7+
namespace StyleCop.Analyzers.DocumentationRules
8+
{
9+
using System.Collections.Immutable;
10+
using System.Composition;
11+
using System.Linq;
12+
using System.Threading;
13+
using System.Threading.Tasks;
14+
using Helpers.ObjectPools;
15+
using Microsoft.CodeAnalysis;
16+
using Microsoft.CodeAnalysis.CodeActions;
17+
using Microsoft.CodeAnalysis.CodeFixes;
18+
using Microsoft.CodeAnalysis.CSharp;
19+
using Microsoft.CodeAnalysis.CSharp.Syntax;
20+
using StyleCop.Analyzers.Helpers;
21+
22+
/// <summary>
23+
/// Implements the code fix for property summary documentation.
24+
/// </summary>
25+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PropertySummaryDocumentationCodeFixProvider))]
26+
[Shared]
27+
public class PropertySummaryDocumentationCodeFixProvider : CodeFixProvider
28+
{
29+
/// <inheritdoc/>
30+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
31+
ImmutableArray.Create(
32+
PropertySummaryDocumentationAnalyzer.SA1623Descriptor.Id,
33+
PropertySummaryDocumentationAnalyzer.SA1624Descriptor.Id);
34+
35+
/// <inheritdoc/>
36+
public override FixAllProvider GetFixAllProvider()
37+
{
38+
return CustomFixAllProviders.BatchFixer;
39+
}
40+
41+
/// <inheritdoc/>
42+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
43+
{
44+
foreach (Diagnostic diagnostic in context.Diagnostics)
45+
{
46+
context.RegisterCodeFix(
47+
CodeAction.Create(
48+
DocumentationResources.PropertySummaryStartTextCodeFix,
49+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
50+
nameof(PropertySummaryDocumentationCodeFixProvider)),
51+
diagnostic);
52+
}
53+
54+
return SpecializedTasks.CompletedTask;
55+
}
56+
57+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
58+
{
59+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
60+
61+
var node = syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
62+
var documentation = node.GetDocumentationCommentTriviaSyntax();
63+
64+
var summaryElement = (XmlElementSyntax)documentation.Content.GetFirstXmlElement(XmlCommentHelper.SummaryXmlTag);
65+
var textElement = (XmlTextSyntax)summaryElement.Content.First();
66+
var textToken = textElement.TextTokens.First(token => token.IsKind(SyntaxKind.XmlTextLiteralToken));
67+
var text = textToken.ValueText;
68+
var newTextBuilder = StringBuilderPool.Allocate();
69+
70+
// preserve leading whitespace
71+
int index = 0;
72+
while (char.IsWhiteSpace(text, index))
73+
{
74+
index++;
75+
}
76+
77+
var preservedWhitespace = text.Substring(0, index);
78+
79+
// process the current documentation string
80+
string modifiedText;
81+
string textToRemove;
82+
if (diagnostic.Properties.TryGetValue(PropertySummaryDocumentationAnalyzer.TextToRemoveKey, out textToRemove))
83+
{
84+
modifiedText = text.Substring(text.IndexOf(textToRemove) + textToRemove.Length).TrimStart();
85+
}
86+
else
87+
{
88+
modifiedText = text.Substring(index);
89+
}
90+
91+
modifiedText = char.ToLowerInvariant(modifiedText[0]) + modifiedText.Substring(1);
92+
93+
// create the new text string
94+
var textToAdd = diagnostic.Properties[PropertySummaryDocumentationAnalyzer.ExpectedTextKey];
95+
var newText = $"{preservedWhitespace}{textToAdd} {modifiedText}";
96+
97+
// replace the token
98+
var newTextTokens = textElement.TextTokens.Replace(textToken, SyntaxFactory.XmlTextLiteral(textToken.LeadingTrivia, newText, newText, textToken.TrailingTrivia));
99+
var newTextElement = textElement.WithTextTokens(newTextTokens);
100+
101+
var newSyntaxRoot = syntaxRoot.ReplaceNode(textElement, newTextElement);
102+
var newDocument = document.WithSyntaxRoot(newSyntaxRoot);
103+
104+
return newDocument;
105+
}
106+
}
107+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
<ItemGroup>
5151
<Compile Include="DocumentationRules\FileHeaderCodeFixProvider.cs" />
5252
<Compile Include="DocumentationRules\InheritdocCodeFixProvider.cs" />
53+
<Compile Include="DocumentationRules\PropertySummaryDocumentationCodeFixProvider.cs" />
5354
<Compile Include="DocumentationRules\SA1609SA1610CodeFixProvider.cs" />
5455
<Compile Include="DocumentationRules\SA1615SA1616CodeFixProvider.cs" />
5556
<Compile Include="DocumentationRules\SA1617CodeFixProvider.cs" />
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.DocumentationRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using StyleCop.Analyzers.DocumentationRules;
12+
using TestHelper;
13+
using Xunit;
14+
15+
/// <summary>
16+
/// This class contains the unit tests for SA1623.
17+
/// </summary>
18+
public class SA1623UnitTests : CodeFixVerifier
19+
{
20+
/// <summary>
21+
/// Verifies that property documentation that does not start with the appropriate text will result in a diagnostic.
22+
/// </summary>
23+
/// <param name="type">The type to use for the property.</param>
24+
/// <param name="accessors">The accessors for the property.</param>
25+
/// <param name="expectedArgument">The expected argument for the diagnostic message.</param>
26+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
27+
[Theory]
28+
[InlineData("int", "{ get; set; }", "Gets or sets")]
29+
[InlineData("int", "{ get; }", "Gets")]
30+
[InlineData("int", "{ get; private set; }", "Gets")]
31+
[InlineData("int", "{ set { } }", "Sets")]
32+
[InlineData("int", "{ private get { return 0; } set { } }", "Sets")]
33+
[InlineData("bool", "{ get; set; }", "Gets or sets a value indicating whether")]
34+
[InlineData("bool", "{ get; }", "Gets a value indicating whether")]
35+
[InlineData("bool", "{ get; private set; }", "Gets a value indicating whether")]
36+
[InlineData("bool", "{ set { } }", "Sets a value indicating whether")]
37+
[InlineData("bool", "{ private get { return false; } set { } }", "Sets a value indicating whether")]
38+
public async Task VerifyDocumentationWithWrongStartingTextWillProduceDiagnosticAsync(string type, string accessors, string expectedArgument)
39+
{
40+
var testCode = $@"
41+
public class TestClass
42+
{{
43+
/// <summary>
44+
/// The first test value.
45+
/// </summary>
46+
public {type} TestProperty {accessors}
47+
}}
48+
";
49+
50+
var fixedTestCode = $@"
51+
public class TestClass
52+
{{
53+
/// <summary>
54+
/// {expectedArgument} the first test value.
55+
/// </summary>
56+
public {type} TestProperty {accessors}
57+
}}
58+
";
59+
60+
var expected = this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1623Descriptor).WithLocation(7, 13 + type.Length).WithArguments(expectedArgument);
61+
62+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
63+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
64+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
65+
}
66+
67+
/// <inheritdoc/>
68+
protected override CodeFixProvider GetCSharpCodeFixProvider()
69+
{
70+
return new PropertySummaryDocumentationCodeFixProvider();
71+
}
72+
73+
/// <inheritdoc/>
74+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
75+
{
76+
yield return new PropertySummaryDocumentationAnalyzer();
77+
}
78+
}
79+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.Test.DocumentationRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using StyleCop.Analyzers.DocumentationRules;
12+
using TestHelper;
13+
using Xunit;
14+
15+
/// <summary>
16+
/// This class contains the unit tests for SA1624.
17+
/// </summary>
18+
public class SA1624UnitTests : CodeFixVerifier
19+
{
20+
/// <summary>
21+
/// Verifies that documentation that starts with the proper text for multiple accessors will produce a diagnostic when one of the accessors has reduced visibility.
22+
/// </summary>
23+
/// <param name="type">The type to use for the property.</param>
24+
/// <param name="accessors">The accessors for the property.</param>
25+
/// <param name="summaryPrefix">The prefix to use in the summary text.</param>
26+
/// <param name="expectedArgument1">The first expected argument for the diagnostic.</param>
27+
/// <param name="expectedArgument2">The second expected argument for the diagnostic.</param>
28+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
29+
[Theory]
30+
[InlineData("int", "get; private set;", "Gets or sets", "get", "Gets")]
31+
[InlineData("int", "private get; set;", "Gets or sets", "set", "Sets")]
32+
[InlineData("bool", "get; private set;", "Gets or sets a value indicating whether", "get", "Gets a value indicating whether")]
33+
[InlineData("bool", "private get; set;", "Gets or sets a value indicating whether", "set", "Sets a value indicating whether")]
34+
public async Task VerifyThatInvalidDocumentationWillReportDiagnosticAsync(string type, string accessors, string summaryPrefix, string expectedArgument1, string expectedArgument2)
35+
{
36+
var testCode = $@"
37+
public class TestClass
38+
{{
39+
/// <summary>
40+
/// {summaryPrefix} the test property.
41+
/// </summary>
42+
public {type} TestProperty
43+
{{
44+
{accessors}
45+
}}
46+
}}
47+
";
48+
49+
var fixedTestCode = $@"
50+
public class TestClass
51+
{{
52+
/// <summary>
53+
/// {expectedArgument2} the test property.
54+
/// </summary>
55+
public {type} TestProperty
56+
{{
57+
{accessors}
58+
}}
59+
}}
60+
";
61+
62+
var expected = this.CSharpDiagnostic(PropertySummaryDocumentationAnalyzer.SA1624Descriptor).WithLocation(7, 13 + type.Length).WithArguments(expectedArgument1, expectedArgument2);
63+
64+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
65+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
66+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
67+
}
68+
69+
/// <summary>
70+
/// 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.
71+
/// </summary>
72+
/// <param name="typeKeyword">The type keyword to use.</param>
73+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
74+
[Theory]
75+
[InlineData("class")]
76+
[InlineData("struct")]
77+
public async Task VerifyPrivateAccessorInPrivateContainerAsync(string typeKeyword)
78+
{
79+
var testCode = $@"
80+
public class ContainerTestClass
81+
{{
82+
private {typeKeyword} ContainerTestType
83+
{{
84+
public {typeKeyword} TestType
85+
{{
86+
/// <summary>
87+
/// Gets or sets the test property.
88+
/// </summary>
89+
public int TestProperty
90+
{{
91+
get; private set;
92+
}}
93+
}}
94+
}}
95+
}}
96+
";
97+
98+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
99+
}
100+
101+
/// <inheritdoc/>
102+
protected override CodeFixProvider GetCSharpCodeFixProvider()
103+
{
104+
return new PropertySummaryDocumentationCodeFixProvider();
105+
}
106+
107+
/// <inheritdoc/>
108+
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
109+
{
110+
yield return new PropertySummaryDocumentationAnalyzer();
111+
}
112+
}
113+
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/StyleCop.Analyzers.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@
140140
<Compile Include="DocumentationRules\SA1620UnitTests.cs" />
141141
<Compile Include="DocumentationRules\SA1621UnitTests.cs" />
142142
<Compile Include="DocumentationRules\SA1622UnitTests.cs" />
143+
<Compile Include="DocumentationRules\SA1623UnitTests.cs" />
144+
<Compile Include="DocumentationRules\SA1624UnitTests.cs" />
143145
<Compile Include="DocumentationRules\SA1626UnitTests.cs" />
144146
<Compile Include="DocumentationRules\SA1627UnitTests.cs" />
145147
<Compile Include="DocumentationRules\SA1628UnitTests.cs" />

0 commit comments

Comments
 (0)