Skip to content

Commit 937f10d

Browse files
committed
Merge pull request #2070 from vweijsters/fix-1986
Implemented single pass fix all for SA1651
2 parents 9079623 + 310a979 commit 937f10d

2 files changed

Lines changed: 77 additions & 8 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1651CodeFixProvider.cs

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
namespace StyleCop.Analyzers.DocumentationRules
55
{
6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Composition;
9+
using System.Linq;
810
using System.Threading;
911
using System.Threading.Tasks;
1012
using Helpers;
1113
using Microsoft.CodeAnalysis;
1214
using Microsoft.CodeAnalysis.CodeActions;
1315
using Microsoft.CodeAnalysis.CodeFixes;
16+
using Microsoft.CodeAnalysis.CSharp;
1417
using Microsoft.CodeAnalysis.CSharp.Syntax;
1518

1619
/// <summary>
@@ -24,14 +27,16 @@ namespace StyleCop.Analyzers.DocumentationRules
2427
[Shared]
2528
internal class SA1651CodeFixProvider : CodeFixProvider
2629
{
30+
private static readonly SyntaxAnnotation NodeToReplaceAnnotation = new SyntaxAnnotation(nameof(NodeToReplaceAnnotation));
31+
2732
/// <inheritdoc/>
2833
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
2934
ImmutableArray.Create(SA1651DoNotUsePlaceholderElements.DiagnosticId);
3035

3136
/// <inheritdoc/>
3237
public override FixAllProvider GetFixAllProvider()
3338
{
34-
return CustomFixAllProviders.BatchFixer;
39+
return FixAll.Instance;
3540
}
3641

3742
/// <inheritdoc/>
@@ -68,13 +73,9 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
6873
}
6974
}
7075

71-
private async Task<Document> GetTransformedDocumentAsync(Document document, XmlElementSyntax elementSyntax, CancellationToken cancellationToken)
76+
private static IEnumerable<XmlNodeSyntax> RemovePlaceHolder(XmlElementSyntax elementSyntax)
7277
{
7378
SyntaxList<XmlNodeSyntax> content = elementSyntax.Content;
74-
if (content.Count == 0)
75-
{
76-
return document;
77-
}
7879

7980
var leadingTrivia = elementSyntax.StartTag.GetLeadingTrivia();
8081
leadingTrivia = leadingTrivia.AddRange(elementSyntax.StartTag.GetTrailingTrivia());
@@ -86,9 +87,77 @@ private async Task<Document> GetTransformedDocumentAsync(Document document, XmlE
8687
trailingTrivia = trailingTrivia.AddRange(elementSyntax.EndTag.GetTrailingTrivia());
8788
content = content.Replace(content[content.Count - 1], content[content.Count - 1].WithTrailingTrivia(trailingTrivia));
8889

90+
return content;
91+
}
92+
93+
private async Task<Document> GetTransformedDocumentAsync(Document document, XmlElementSyntax elementSyntax, CancellationToken cancellationToken)
94+
{
95+
if (elementSyntax.Content.Count == 0)
96+
{
97+
return document;
98+
}
99+
89100
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
90-
SyntaxNode newRoot = root.ReplaceNode(elementSyntax, content);
101+
SyntaxNode newRoot = root.ReplaceNode(elementSyntax, RemovePlaceHolder(elementSyntax));
91102
return document.WithSyntaxRoot(newRoot);
92103
}
104+
105+
private class FixAll : DocumentBasedFixAllProvider
106+
{
107+
public static FixAll Instance { get; } = new FixAll();
108+
109+
protected override string CodeActionTitle { get; } = DocumentationResources.SA1651CodeFix;
110+
111+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
112+
{
113+
var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
114+
var elements = new List<XmlElementSyntax>();
115+
116+
foreach (var diagnostic in diagnostics)
117+
{
118+
var xmlElement = syntaxRoot.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true) as XmlElementSyntax;
119+
120+
if ((xmlElement != null)
121+
&& (xmlElement.Content.Count > 0)
122+
&& !string.IsNullOrWhiteSpace(xmlElement.Content.ToString()))
123+
{
124+
elements.Add(xmlElement);
125+
}
126+
}
127+
128+
var newSyntaxRoot = syntaxRoot.ReplaceNodes(elements, (original, rewritten) => rewritten.WithAdditionalAnnotations(NodeToReplaceAnnotation));
129+
newSyntaxRoot = new FixAllVisitor().Visit(newSyntaxRoot);
130+
return newSyntaxRoot;
131+
}
132+
}
133+
134+
private class FixAllVisitor : CSharpSyntaxRewriter
135+
{
136+
public FixAllVisitor()
137+
: base(true)
138+
{
139+
}
140+
141+
public override SyntaxList<TNode> VisitList<TNode>(SyntaxList<TNode> list)
142+
{
143+
list = base.VisitList(list);
144+
145+
var index = 0;
146+
while (index < list.Count)
147+
{
148+
var element = list[index];
149+
if (element.HasAnnotation(NodeToReplaceAnnotation))
150+
{
151+
list = list.ReplaceRange(element, RemovePlaceHolder(element as XmlElementSyntax).Cast<TNode>());
152+
}
153+
else
154+
{
155+
index++;
156+
}
157+
}
158+
159+
return list;
160+
}
161+
}
93162
}
94163
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/DocumentationRules/SA1651UnitTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ public class ClassName
226226

227227
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
228228
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
229-
await this.VerifyCSharpFixAsync(testCode, fixedCode, numberOfFixAllIterations: 2, cancellationToken: CancellationToken.None).ConfigureAwait(false);
229+
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
230230
}
231231

232232
protected override IEnumerable<DiagnosticAnalyzer> GetCSharpDiagnosticAnalyzers()

0 commit comments

Comments
 (0)