Skip to content

Commit

Permalink
Merge pull request #1439 from sharwell/sa1004-fixall
Browse files Browse the repository at this point in the history
Re-implement the SA1004 code fix using efficient text manipulation
  • Loading branch information
sharwell committed Sep 10, 2015
2 parents 5922dc1 + f0b09bf commit b946558
Showing 1 changed file with 57 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@

namespace StyleCop.Analyzers.SpacingRules
{
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;

/// <summary>
/// Implements a code fix for <see cref="SA1004DocumentationLinesMustBeginWithSingleSpace"/>.
Expand All @@ -23,11 +26,9 @@ namespace StyleCop.Analyzers.SpacingRules
[Shared]
public class SA1004CodeFixProvider : CodeFixProvider
{
private static readonly ImmutableArray<string> FixableDiagnostics =
ImmutableArray.Create(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId);

/// <inheritdoc/>
public override ImmutableArray<string> FixableDiagnosticIds => FixableDiagnostics;
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId);

/// <inheritdoc/>
public override FixAllProvider GetFixAllProvider()
Expand All @@ -36,38 +37,77 @@ public override FixAllProvider GetFixAllProvider()
}

/// <inheritdoc/>
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

foreach (var diagnostic in context.Diagnostics.Where(d => FixableDiagnostics.Contains(d.Id)))
foreach (var diagnostic in context.Diagnostics)
{
if (!diagnostic.Id.Equals(SA1004DocumentationLinesMustBeginWithSingleSpace.DiagnosticId))
{
continue;
}

context.RegisterCodeFix(CodeAction.Create(SpacingResources.SA1004CodeFix, token => GetTransformedDocumentAsync(context.Document, root, diagnostic), equivalenceKey: nameof(SA1004CodeFixProvider)), diagnostic);
context.RegisterCodeFix(
CodeAction.Create(
SpacingResources.SA1004CodeFix,
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
equivalenceKey: nameof(SA1004CodeFixProvider)),
diagnostic);
}

return SpecializedTasks.CompletedTask;
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
return document.WithText(text.WithChanges(GetTextChange(root, text, diagnostic)));
}

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

default:
updatedToken = token.WithLeadingTrivia(token.LeadingTrivia.Add(SyntaxFactory.Space));
break;
return new TextChange(new TextSpan(token.SpanStart, 0), " ");
}
}

private class FixAll : DocumentBasedFixAllProvider
{
public static FixAllProvider Instance { get; } =
new FixAll();

protected override string CodeActionTitle =>
SpacingResources.SA1004CodeFix;

Document updatedDocument = document.WithSyntaxRoot(root.ReplaceToken(token, updatedToken));
return Task.FromResult(updatedDocument);
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
{
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
if (diagnostics.IsEmpty)
{
return null;
}

var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
var text = await document.GetTextAsync(fixAllContext.CancellationToken).ConfigureAwait(false);

List<TextChange> changes = new List<TextChange>();
foreach (var diagnostic in diagnostics)
{
changes.Add(GetTextChange(root, text, diagnostic));
}

changes.Sort((left, right) => left.Span.Start.CompareTo(right.Span.Start));

var tree = await document.GetSyntaxTreeAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
return await tree.WithChangedText(text.WithChanges(changes)).GetRootAsync().ConfigureAwait(false);
}
}
}
}

0 comments on commit b946558

Please sign in to comment.