Skip to content

Commit f0b09bf

Browse files
committed
Re-implement the SA1004 code fix using efficient text manipulation
1 parent 526ebb0 commit f0b09bf

1 file changed

Lines changed: 57 additions & 17 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers/SpacingRules/SA1004CodeFixProvider.cs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33

44
namespace StyleCop.Analyzers.SpacingRules
55
{
6+
using System.Collections.Generic;
67
using System.Collections.Immutable;
78
using System.Composition;
89
using System.Linq;
10+
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;
1416
using Microsoft.CodeAnalysis.CSharp;
17+
using Microsoft.CodeAnalysis.Text;
1518

1619
/// <summary>
1720
/// Implements a code fix for <see cref="SA1004DocumentationLinesMustBeginWithSingleSpace"/>.
@@ -23,11 +26,9 @@ namespace StyleCop.Analyzers.SpacingRules
2326
[Shared]
2427
public class SA1004CodeFixProvider : CodeFixProvider
2528
{
26-
private static readonly ImmutableArray<string> FixableDiagnostics =
27-
ImmutableArray.Create(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId);
28-
2929
/// <inheritdoc/>
30-
public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;
30+
public override ImmutableArray<string> FixableDiagnosticIds { get; }
31+
= ImmutableArray.Create(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId);
3132

3233
/// <inheritdoc/>
3334
public override FixAllProvider GetFixAllProvider()
@@ -36,38 +37,77 @@ public override FixAllProvider GetFixAllProvider()
3637
}
3738

3839
/// <inheritdoc/>
39-
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
40+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
4041
{
41-
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
42-
43-
foreach (var diagnostic in context.Diagnostics.Where(d => FixableDiagnostics.Contains(d.Id)))
42+
foreach (var diagnostic in context.Diagnostics)
4443
{
4544
if (!diagnostic.Id.Equals(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId))
4645
{
4746
continue;
4847
}
4948

50-
context.RegisterCodeFix(CodeAction.Create(SpacingResources.SA1004CodeFix, token => GetTransformedDocumentAsync(context.Document, root, diagnostic), equivalenceKey: nameof(SA1004CodeFixProvider)), diagnostic);
49+
context.RegisterCodeFix(
50+
CodeAction.Create(
51+
SpacingResources.SA1004CodeFix,
52+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
53+
equivalenceKey: nameof(SA1004CodeFixProvider)),
54+
diagnostic);
5155
}
56+
57+
return SpecializedTasks.CompletedTask;
58+
}
59+
60+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
61+
{
62+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
63+
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
64+
return document.WithText(text.WithChanges(GetTextChange(root, text, diagnostic)));
5265
}
5366

54-
private static Task<Document> GetTransformedDocumentAsync(Document document, SyntaxNode root, Diagnostic diagnostic)
67+
private static TextChange GetTextChange(SyntaxNode root, SourceText sourceText, Diagnostic diagnostic)
5568
{
5669
var token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
57-
SyntaxToken updatedToken;
5870
switch (token.Kind())
5971
{
6072
case SyntaxKind.XmlTextLiteralToken:
61-
updatedToken = XmlSyntaxFactory.TextLiteral(" " + token.Text.TrimStart(' ')).WithTriviaFrom(token);
62-
break;
73+
int spaceCount = token.ValueText.Length - token.ValueText.TrimStart(' ').Length;
74+
return new TextChange(new TextSpan(token.SpanStart, spaceCount), " ");
6375

6476
default:
65-
updatedToken = token.WithLeadingTrivia(token.LeadingTrivia.Add(SyntaxFactory.Space));
66-
break;
77+
return new TextChange(new TextSpan(token.SpanStart, 0), " ");
6778
}
79+
}
80+
81+
private class FixAll : DocumentBasedFixAllProvider
82+
{
83+
public static FixAllProvider Instance { get; } =
84+
new FixAll();
85+
86+
protected override string CodeActionTitle =>
87+
SpacingResources.SA1004CodeFix;
6888

69-
Document updatedDocument = document.WithSyntaxRoot(root.ReplaceToken(token, updatedToken));
70-
return Task.FromResult(updatedDocument);
89+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
90+
{
91+
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
92+
if (diagnostics.IsEmpty)
93+
{
94+
return null;
95+
}
96+
97+
var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
98+
var text = await document.GetTextAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
99+
100+
List<TextChange> changes = new List<TextChange>();
101+
foreach (var diagnostic in diagnostics)
102+
{
103+
changes.Add(GetTextChange(root, text, diagnostic));
104+
}
105+
106+
changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start));
107+
108+
var tree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
109+
return await tree.WithChangedText(text.WithChanges(changes)).GetRootAsync().ConfigureAwait(false);
110+
}
71111
}
72112
}
73113
}

0 commit comments

Comments
 (0)