Skip to content

Commit 5922dc1

Browse files
committed
Merge pull request #1441 from sharwell/fix-655
Implement a code fix provider for SA1626 (SingleLineCommentsMustNotUseDocumentationStyleSlashes)
2 parents 0551ee6 + ee13fae commit 5922dc1

6 files changed

Lines changed: 168 additions & 17 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1626UnitTests.cs

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,23 @@ namespace StyleCop.Analyzers.Test.DocumentationRules
66
using System.Collections.Generic;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis.CodeFixes;
910
using Microsoft.CodeAnalysis.Diagnostics;
1011
using StyleCop.Analyzers.DocumentationRules;
1112
using TestHelper;
1213
using Xunit;
1314

14-
public class SA1626UnitTests : DiagnosticVerifier
15+
public class SA1626UnitTests : CodeFixVerifier
1516
{
1617
private const string DiagnosticId = SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes.DiagnosticId;
1718

1819
[Fact]
1920
public async Task TestClassWithXmlCommentAsync()
2021
{
2122
var testCode = @"/// <summary>
22-
/// Xml Documentation
23+
/// XML Documentation
2324
/// </summary>
24-
public class Foo
25+
public class TypeName
2526
{
2627
public void Bar()
2728
{
@@ -34,7 +35,7 @@ public void Bar()
3435
[Fact]
3536
public async Task TestMethodWithCommentAsync()
3637
{
37-
var testCode = @"public class Foo
38+
var testCode = @"public class TypeName
3839
{
3940
public void Bar()
4041
{
@@ -48,26 +49,34 @@ public void Bar()
4849
[Fact]
4950
public async Task TestMethodWithOneLineThreeSlashCommentAsync()
5051
{
51-
var testCode = @"public class Foo
52+
var testCode = @"public class TypeName
5253
{
5354
public void Bar()
5455
{
5556
/// This is a comment
5657
}
5758
}
5859
";
59-
var expected = new[]
60-
{
61-
this.CSharpDiagnostic().WithLocation(5, 9)
62-
};
60+
var fixedCode = @"public class TypeName
61+
{
62+
public void Bar()
63+
{
64+
// This is a comment
65+
}
66+
}
67+
";
68+
69+
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, 9);
6370

6471
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
72+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
73+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
6574
}
6675

6776
[Fact]
6877
public async Task TestMethodWithMultiLineThreeSlashCommentAsync()
6978
{
70-
var testCode = @"public class Foo
79+
var testCode = @"public class TypeName
7180
{
7281
public void Bar()
7382
{
@@ -76,18 +85,31 @@ public void Bar()
7685
}
7786
}
7887
";
79-
var expected = new[]
88+
var fixedCode = @"public class TypeName
89+
{
90+
public void Bar()
91+
{
92+
// This is
93+
// a comment
94+
}
95+
}
96+
";
97+
98+
DiagnosticResult[] expected =
8099
{
81-
this.CSharpDiagnostic().WithLocation(5, 9)
100+
this.CSharpDiagnostic().WithLocation(5, 9),
101+
this.CSharpDiagnostic().WithLocation(6, 9),
82102
};
83103

84104
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
105+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
106+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
85107
}
86108

87109
[Fact]
88110
public async Task TestMethodWithCodeCommentsAsync()
89111
{
90-
var testCode = @"public class Foo
112+
var testCode = @"public class TypeName
91113
{
92114
public void Bar()
93115
{
@@ -101,7 +123,7 @@ public void Bar()
101123
[Fact]
102124
public async Task TestMethodWithSingeLineDocumentationAsync()
103125
{
104-
var testCode = @"public class Foo
126+
var testCode = @"public class TypeName
105127
{
106128
/// <summary>Summary text</summary>
107129
public void Bar()
@@ -112,9 +134,16 @@ public void Bar()
112134
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
113135
}
114136

137+
/// <inheritdoc/>
115138
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()
116139
{
117140
yield return new SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes();
118141
}
142+
143+
/// <inheritdoc/>
144+
protected override CodeFixProvider GetCSharpCodeFixProvider()
145+
{
146+
return new SA1626CodeFixProvider();
147+
}
119148
}
120149
}

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/DocumentationResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@
126126
<data name="SA1617CodeFix" xml:space="preserve">
127127
<value>Remove &lt;returns&gt; XML comment</value>
128128
</data>
129+
<data name="SA1626CodeFix" xml:space="preserve">
130+
<value>Convert to line comment</value>
131+
</data>
129132
<data name="SA1633CodeFix" xml:space="preserve">
130133
<value>Add file header</value>
131134
</data>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.DocumentationRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CodeActions;
13+
using Microsoft.CodeAnalysis.CodeFixes;
14+
using Microsoft.CodeAnalysis.Text;
15+
using StyleCop.Analyzers.Helpers;
16+
17+
/// <summary>
18+
/// Implements a code fix for <see cref="SA1626CodeFixProvider"/>.
19+
/// </summary>
20+
/// <remarks>
21+
/// <para>To fix a violation of this rule, remove a slash from the beginning of the comment so that it begins with
22+
/// only two slashes.</para>
23+
/// </remarks>
24+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1626CodeFixProvider))]
25+
[Shared]
26+
public class SA1626CodeFixProvider : CodeFixProvider
27+
{
28+
/// <inheritdoc/>
29+
public override ImmutableArray<string> FixableDiagnosticIds { get; }
30+
= ImmutableArray.Create(SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes.DiagnosticId);
31+
32+
/// <inheritdoc/>
33+
public override FixAllProvider GetFixAllProvider()
34+
{
35+
return FixAll.Instance;
36+
}
37+
38+
/// <inheritdoc/>
39+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
40+
{
41+
foreach (Diagnostic diagnostic in context.Diagnostics)
42+
{
43+
if (diagnostic.Id != SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes.DiagnosticId)
44+
{
45+
continue;
46+
}
47+
48+
context.RegisterCodeFix(
49+
CodeAction.Create(
50+
DocumentationResources.SA1626CodeFix,
51+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
52+
equivalenceKey: nameof(SA1626CodeFixProvider)),
53+
diagnostic);
54+
}
55+
56+
return SpecializedTasks.CompletedTask;
57+
}
58+
59+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
60+
{
61+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
62+
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
63+
64+
TextChange textChange = new TextChange(new TextSpan(diagnostic.Location.SourceSpan.Start, 1), string.Empty);
65+
return document.WithText(text.WithChanges(textChange));
66+
}
67+
68+
private class FixAll : DocumentBasedFixAllProvider
69+
{
70+
public static FixAllProvider Instance { get; } =
71+
new FixAll();
72+
73+
protected override string CodeActionTitle =>
74+
DocumentationResources.SA1626CodeFix;
75+
76+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
77+
{
78+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
79+
if (diagnostics.IsEmpty)
80+
{
81+
return null;
82+
}
83+
84+
var text = await document.GetTextAsync().ConfigureAwait(false);
85+
86+
List<TextChange> changes = new List<TextChange>();
87+
foreach (var diagnostic in diagnostics)
88+
{
89+
var sourceSpan = diagnostic.Location.SourceSpan;
90+
changes.Add(new TextChange(new TextSpan(sourceSpan.Start, 1), string.Empty));
91+
}
92+
93+
changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start));
94+
95+
var tree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);
96+
return await tree.WithChangedText(text.WithChanges(changes)).GetRootAsync().ConfigureAwait(false);
97+
}
98+
}
99+
}
100+
}

StyleCop.Analyzers/StyleCop.Analyzers/DocumentationRules/SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace StyleCop.Analyzers.DocumentationRules
99
using Microsoft.CodeAnalysis.CSharp;
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
1111
using Microsoft.CodeAnalysis.Diagnostics;
12+
using Microsoft.CodeAnalysis.Text;
1213

1314
/// <summary>
1415
/// The C# code contains a single-line comment which begins with three forward slashes in a row.
@@ -89,10 +90,18 @@ private static void HandleSingleLineDocumentationTrivia(SyntaxNodeAnalysisContex
8990
// Check if the comment is not multi line
9091
if (node.Content.All(x => x.IsKind(SyntaxKind.XmlText)))
9192
{
92-
// Add a diagnostic on '///'
93-
var trivia = context.Node.GetLeadingTrivia().First();
93+
foreach (var trivia in node.DescendantTrivia(descendIntoTrivia: true))
94+
{
95+
if (!trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia))
96+
{
97+
continue;
98+
}
9499

95-
context.ReportDiagnostic(Diagnostic.Create(Descriptor, trivia.GetLocation()));
100+
// Add a diagnostic on '///'
101+
TextSpan location = trivia.GetLocation().SourceSpan;
102+
TextSpan slashes = TextSpan.FromBounds(location.End - 3, location.End);
103+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, Location.Create(trivia.SyntaxTree, slashes)));
104+
}
96105
}
97106
}
98107
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<Compile Include="DocumentationRules\SA1623PropertySummaryDocumentationMustMatchAccessors.cs" />
8989
<Compile Include="DocumentationRules\SA1624PropertySummaryDocumentationMustOmitSetAccessorWithRestrictedAccess.cs" />
9090
<Compile Include="DocumentationRules\SA1625ElementDocumentationMustNotBeCopiedAndPasted.cs" />
91+
<Compile Include="DocumentationRules\SA1626CodeFixProvider.cs" />
9192
<Compile Include="DocumentationRules\SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes.cs" />
9293
<Compile Include="DocumentationRules\SA1627DocumentationTextMustNotBeEmpty.cs" />
9394
<Compile Include="DocumentationRules\SA1628DocumentationTextMustBeginWithACapitalLetter.cs" />

0 commit comments

Comments
 (0)