Skip to content

Commit 4d9b3e3

Browse files
authored
Merge pull request #3322 from sharwell/custom-fix
Implement custom fix all providers
2 parents ab5993f + 6ac3ef3 commit 4d9b3e3

File tree

6 files changed

+199
-83
lines changed

6 files changed

+199
-83
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1506CodeFixProvider.cs

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules
55
{
66
using System.Collections.Immutable;
77
using System.Composition;
8+
using System.Linq;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.CodeAnalysis;
@@ -27,7 +28,7 @@ internal class SA1506CodeFixProvider : CodeFixProvider
2728
/// <inheritdoc/>
2829
public override FixAllProvider GetFixAllProvider()
2930
{
30-
return CustomFixAllProviders.BatchFixer;
31+
return FixAll.Instance;
3132
}
3233

3334
/// <inheritdoc/>
@@ -48,53 +49,72 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4849

4950
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
5051
{
51-
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
52+
var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false);
53+
return document.WithSyntaxRoot(newRoot);
54+
}
5255

53-
var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
54-
var triviaList = token.LeadingTrivia;
56+
private static async Task<SyntaxNode> GetTransformedDocumentAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
57+
{
58+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
59+
return syntaxRoot.ReplaceTokens(
60+
diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)),
61+
(originalToken, rewrittenToken) =>
62+
{
63+
var triviaList = rewrittenToken.LeadingTrivia;
5564

56-
var index = triviaList.IndexOf(SyntaxKind.SingleLineDocumentationCommentTrivia);
65+
var index = triviaList.IndexOf(SyntaxKind.SingleLineDocumentationCommentTrivia);
5766

58-
int currentLineStart = index + 1;
59-
bool onBlankLine = true;
60-
for (int currentIndex = currentLineStart; currentIndex < triviaList.Count; currentIndex++)
61-
{
62-
switch (triviaList[currentIndex].Kind())
63-
{
64-
case SyntaxKind.EndOfLineTrivia:
65-
if (onBlankLine)
67+
int currentLineStart = index + 1;
68+
bool onBlankLine = true;
69+
for (int currentIndex = currentLineStart; currentIndex < triviaList.Count; currentIndex++)
6670
{
67-
triviaList = triviaList.RemoveRange(currentLineStart, currentIndex - currentLineStart + 1);
68-
currentIndex = currentLineStart - 1;
69-
continue;
70-
}
71-
else
72-
{
73-
currentLineStart = currentIndex + 1;
74-
onBlankLine = true;
75-
break;
76-
}
71+
switch (triviaList[currentIndex].Kind())
72+
{
73+
case SyntaxKind.EndOfLineTrivia:
74+
if (onBlankLine)
75+
{
76+
triviaList = triviaList.RemoveRange(currentLineStart, currentIndex - currentLineStart + 1);
77+
currentIndex = currentLineStart - 1;
78+
continue;
79+
}
80+
else
81+
{
82+
currentLineStart = currentIndex + 1;
83+
onBlankLine = true;
84+
break;
85+
}
7786

78-
case SyntaxKind.WhitespaceTrivia:
79-
break;
87+
case SyntaxKind.WhitespaceTrivia:
88+
break;
8089

81-
default:
82-
if (triviaList[currentIndex].HasBuiltinEndLine())
83-
{
84-
currentLineStart = currentIndex + 1;
85-
onBlankLine = true;
86-
break;
87-
}
88-
else
89-
{
90-
onBlankLine = false;
91-
break;
90+
default:
91+
if (triviaList[currentIndex].HasBuiltinEndLine())
92+
{
93+
currentLineStart = currentIndex + 1;
94+
onBlankLine = true;
95+
break;
96+
}
97+
else
98+
{
99+
onBlankLine = false;
100+
break;
101+
}
102+
}
92103
}
93-
}
94-
}
95104

96-
var newSyntaxRoot = syntaxRoot.ReplaceToken(token, token.WithLeadingTrivia(triviaList));
97-
return document.WithSyntaxRoot(newSyntaxRoot);
105+
return rewrittenToken.WithLeadingTrivia(triviaList);
106+
});
107+
}
108+
109+
private class FixAll : DocumentBasedFixAllProvider
110+
{
111+
public static FixAllProvider Instance { get; } =
112+
new FixAll();
113+
114+
protected override string CodeActionTitle => LayoutResources.SA1506CodeFix;
115+
116+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
117+
=> await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
98118
}
99119
}
100120
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1509CodeFixProvider.cs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal class SA1509CodeFixProvider : CodeFixProvider
2929
/// <inheritdoc/>
3030
public override FixAllProvider GetFixAllProvider()
3131
{
32-
return CustomFixAllProviders.BatchFixer;
32+
return FixAll.Instance;
3333
}
3434

3535
/// <inheritdoc/>
@@ -40,33 +40,41 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4040
context.RegisterCodeFix(
4141
CodeAction.Create(
4242
LayoutResources.SA1509CodeFix,
43-
token => this.GetTransformedDocumentAsync(context.Document, diagnostic, token),
43+
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
4444
nameof(SA1509CodeFixProvider)),
4545
diagnostic);
4646
}
4747

4848
return SpecializedTasks.CompletedTask;
4949
}
5050

51-
private async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
51+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
5252
{
53-
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
54-
55-
var openBrace = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
56-
var leadingTrivia = openBrace.LeadingTrivia;
53+
var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false);
54+
return document.WithSyntaxRoot(newRoot);
55+
}
5756

58-
var newTriviaList = SyntaxFactory.TriviaList();
57+
private static async Task<SyntaxNode> GetTransformedDocumentAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
58+
{
59+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
60+
return syntaxRoot.ReplaceTokens(
61+
diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)),
62+
(originalToken, rewrittenToken) =>
63+
{
64+
var openBrace = rewrittenToken;
65+
var leadingTrivia = openBrace.LeadingTrivia;
5966

60-
var previousEmptyLines = this.GetPreviousEmptyLines(openBrace).ToList();
61-
newTriviaList = newTriviaList.AddRange(leadingTrivia.Except(previousEmptyLines));
67+
var newTriviaList = SyntaxFactory.TriviaList();
6268

63-
var newOpenBrace = openBrace.WithLeadingTrivia(newTriviaList);
64-
var newSyntaxRoot = syntaxRoot.ReplaceToken(openBrace, newOpenBrace);
69+
var previousEmptyLines = GetPreviousEmptyLines(openBrace).ToList();
70+
newTriviaList = newTriviaList.AddRange(leadingTrivia.Except(previousEmptyLines));
6571

66-
return document.WithSyntaxRoot(newSyntaxRoot);
72+
var newOpenBrace = openBrace.WithLeadingTrivia(newTriviaList);
73+
return newOpenBrace;
74+
});
6775
}
6876

69-
private IEnumerable<SyntaxTrivia> GetPreviousEmptyLines(SyntaxToken openBrace)
77+
private static IEnumerable<SyntaxTrivia> GetPreviousEmptyLines(SyntaxToken openBrace)
7078
{
7179
var result = new List<SyntaxTrivia>();
7280

@@ -92,5 +100,16 @@ private IEnumerable<SyntaxTrivia> GetPreviousEmptyLines(SyntaxToken openBrace)
92100

93101
return result;
94102
}
103+
104+
private class FixAll : DocumentBasedFixAllProvider
105+
{
106+
public static FixAllProvider Instance { get; } =
107+
new FixAll();
108+
109+
protected override string CodeActionTitle => LayoutResources.SA1509CodeFix;
110+
111+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
112+
=> await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
113+
}
95114
}
96115
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1510CodeFixProvider.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules
55
{
66
using System.Collections.Immutable;
77
using System.Composition;
8+
using System.Linq;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.CodeAnalysis;
@@ -26,7 +27,7 @@ internal class SA1510CodeFixProvider : CodeFixProvider
2627
/// <inheritdoc/>
2728
public override FixAllProvider GetFixAllProvider()
2829
{
29-
return CustomFixAllProviders.BatchFixer;
30+
return FixAll.Instance;
3031
}
3132

3233
/// <inheritdoc/>
@@ -46,13 +47,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4647
}
4748

4849
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
50+
{
51+
var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false);
52+
return document.WithSyntaxRoot(newRoot);
53+
}
54+
55+
private static async Task<SyntaxNode> GetTransformedDocumentAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
4956
{
5057
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
58+
return syntaxRoot.ReplaceTokens(
59+
diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)),
60+
(originalToken, rewrittenToken) =>
61+
{
62+
return rewrittenToken.WithoutLeadingBlankLines();
63+
});
64+
}
65+
66+
private class FixAll : DocumentBasedFixAllProvider
67+
{
68+
public static FixAllProvider Instance { get; } =
69+
new FixAll();
5170

52-
var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
71+
protected override string CodeActionTitle => LayoutResources.SA1510CodeFix;
5372

54-
var newSyntaxRoot = syntaxRoot.ReplaceToken(token, token.WithoutLeadingBlankLines());
55-
return document.WithSyntaxRoot(newSyntaxRoot);
73+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
74+
=> await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
5675
}
5776
}
5877
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1511CodeFixProvider.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules
55
{
66
using System.Collections.Immutable;
77
using System.Composition;
8+
using System.Linq;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.CodeAnalysis;
@@ -26,7 +27,7 @@ internal class SA1511CodeFixProvider : CodeFixProvider
2627
/// <inheritdoc/>
2728
public override FixAllProvider GetFixAllProvider()
2829
{
29-
return CustomFixAllProviders.BatchFixer;
30+
return FixAll.Instance;
3031
}
3132

3233
/// <inheritdoc/>
@@ -46,13 +47,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
4647
}
4748

4849
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
50+
{
51+
var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false);
52+
return document.WithSyntaxRoot(newRoot);
53+
}
54+
55+
private static async Task<SyntaxNode> GetTransformedDocumentAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
4956
{
5057
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
58+
return syntaxRoot.ReplaceTokens(
59+
diagnostics.Select(diagnostic => syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start)),
60+
(originalToken, rewrittenToken) =>
61+
{
62+
return rewrittenToken.WithoutLeadingBlankLines();
63+
});
64+
}
65+
66+
private class FixAll : DocumentBasedFixAllProvider
67+
{
68+
public static FixAllProvider Instance { get; } =
69+
new FixAll();
5170

52-
var whileToken = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.Start);
71+
protected override string CodeActionTitle => LayoutResources.SA1511CodeFix;
5372

54-
var newSyntaxRoot = syntaxRoot.ReplaceToken(whileToken, whileToken.WithoutLeadingBlankLines());
55-
return document.WithSyntaxRoot(newSyntaxRoot);
73+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
74+
=> await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
5675
}
5776
}
5877
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/LayoutRules/SA1513CodeFixProvider.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.LayoutRules
55
{
66
using System.Collections.Immutable;
77
using System.Composition;
8+
using System.Linq;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Microsoft.CodeAnalysis;
@@ -30,7 +31,7 @@ internal class SA1513CodeFixProvider : CodeFixProvider
3031
/// <inheritdoc/>
3132
public override FixAllProvider GetFixAllProvider()
3233
{
33-
return CustomFixAllProviders.BatchFixer;
34+
return FixAll.Instance;
3435
}
3536

3637
/// <inheritdoc/>
@@ -51,15 +52,31 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
5152

5253
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
5354
{
54-
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
55-
var token = syntaxRoot.FindToken(diagnostic.Location.SourceSpan.End);
55+
var newRoot = await GetTransformedDocumentAsync(document, ImmutableArray.Create(diagnostic), cancellationToken).ConfigureAwait(false);
56+
return document.WithSyntaxRoot(newRoot);
57+
}
58+
59+
private static async Task<SyntaxNode> GetTransformedDocumentAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
60+
{
61+
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
62+
return root.ReplaceTokens(
63+
diagnostics.Select(diagnostic => root.FindToken(diagnostic.Location.SourceSpan.End)),
64+
(originalToken, rewrittenToken) =>
65+
{
66+
var newTrivia = rewrittenToken.LeadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed);
67+
return rewrittenToken.WithLeadingTrivia(newTrivia);
68+
});
69+
}
70+
71+
private class FixAll : DocumentBasedFixAllProvider
72+
{
73+
public static FixAllProvider Instance { get; } =
74+
new FixAll();
5675

57-
var newTrivia = token.LeadingTrivia.Insert(0, SyntaxFactory.CarriageReturnLineFeed);
58-
var newToken = token.WithLeadingTrivia(newTrivia);
59-
var newSyntaxRoot = syntaxRoot.ReplaceToken(token, newToken);
60-
var newDocument = document.WithSyntaxRoot(newSyntaxRoot);
76+
protected override string CodeActionTitle => LayoutResources.SA1513CodeFix;
6177

62-
return newDocument;
78+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
79+
=> await GetTransformedDocumentAsync(document, diagnostics, fixAllContext.CancellationToken).ConfigureAwait(false);
6380
}
6481
}
6582
}

0 commit comments

Comments
 (0)