Skip to content

Commit c71c129

Browse files
committed
Fix SA1107 implicit reliance on default line endings
1 parent c9c70d5 commit c71c129

File tree

3 files changed

+61
-27
lines changed

3 files changed

+61
-27
lines changed

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1107CodeFixProvider.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55

66
namespace StyleCop.Analyzers.ReadabilityRules
77
{
8+
using System.Collections.Generic;
89
using System.Collections.Immutable;
910
using System.Composition;
1011
using System.Diagnostics;
12+
using System.Threading;
1113
using System.Threading.Tasks;
1214
using Microsoft.CodeAnalysis;
1315
using Microsoft.CodeAnalysis.CodeActions;
1416
using Microsoft.CodeAnalysis.CodeFixes;
15-
using Microsoft.CodeAnalysis.CSharp;
1617
using Microsoft.CodeAnalysis.CSharp.Syntax;
18+
using StyleCop.Analyzers.Helpers;
1719

1820
/// <summary>
1921
/// Implements a code fix for <see cref="SA1107CodeMustNotContainMultipleStatementsOnOneLine"/>.
@@ -51,22 +53,40 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
5153
context.RegisterCodeFix(
5254
CodeAction.Create(
5355
ReadabilityResources.SA1107CodeFix,
54-
cancellationToken => GetTransformedDocumentAsync(context.Document, root, node),
56+
cancellationToken => GetTransformedDocumentAsync(context.Document, root, node, cancellationToken),
5557
nameof(SA1107CodeFixProvider)),
5658
diagnostic);
5759
}
5860
}
5961
}
6062

61-
private static Task<Document> GetTransformedDocumentAsync(Document document, SyntaxNode root, SyntaxNode node)
63+
private static async Task<Document> GetTransformedDocumentAsync(Document document, SyntaxNode root, SyntaxNode node, CancellationToken cancellationToken)
6264
{
6365
SyntaxNode newSyntaxRoot = root;
6466
Debug.Assert(!node.HasLeadingTrivia, "The trivia should be trailing trivia of the previous node");
6567

66-
SyntaxNode newNode = node.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed);
67-
newSyntaxRoot = newSyntaxRoot.ReplaceNode(node, newNode);
68+
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
69+
var options = document.Project.Solution.Workspace.Options;
70+
var firstToken = node.GetFirstToken();
71+
var endOfLine = FormattingHelper.GetEndOfLineForCodeFix(firstToken, text, options);
72+
var settings = SettingsHelper.GetStyleCopSettingsInCodeFix(document.Project.AnalyzerOptions, root.SyntaxTree, cancellationToken);
6873

69-
return Task.FromResult(document.WithSyntaxRoot(newSyntaxRoot));
74+
var firstTokenOnLine = IndentationHelper.GetFirstTokenOnTextLine(firstToken);
75+
var previousToken = firstToken.GetPreviousToken(includeZeroWidth: true);
76+
var replacementPreviousToken = previousToken.WithTrailingTrivia(previousToken.TrailingTrivia.WithoutTrailingWhitespace().Add(endOfLine));
77+
var indentSteps = IndentationHelper.GetIndentationSteps(settings.Indentation, firstTokenOnLine);
78+
var indentTrivia = IndentationHelper.GenerateWhitespaceTrivia(settings.Indentation, indentSteps);
79+
80+
var replacementTokens = new Dictionary<SyntaxToken, SyntaxToken>()
81+
{
82+
[previousToken] = replacementPreviousToken,
83+
[firstToken] = firstToken.WithLeadingTrivia(indentTrivia),
84+
};
85+
86+
var newRoot = root.ReplaceTokens(
87+
new[] { previousToken, firstToken },
88+
(originalToken, rewrittenToken) => replacementTokens[originalToken]);
89+
return document.WithSyntaxRoot(newRoot);
7090
}
7191
}
7292
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/ReadabilityRules/SA1107FixAllProvider.cs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@
55

66
namespace StyleCop.Analyzers.ReadabilityRules
77
{
8+
using System.Collections.Generic;
89
using System.Collections.Immutable;
910
using System.Threading.Tasks;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CodeFixes;
12-
using Microsoft.CodeAnalysis.CSharp;
13-
using Microsoft.CodeAnalysis.Editing;
1413
using StyleCop.Analyzers.Helpers;
1514

1615
internal class SA1107FixAllProvider : DocumentBasedFixAllProvider
@@ -24,9 +23,10 @@ protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fi
2423
return null;
2524
}
2625

27-
DocumentEditor editor = await DocumentEditor.CreateAsync(document, fixAllContext.CancellationToken).ConfigureAwait(false);
28-
29-
SyntaxNode root = editor.GetChangedRoot();
26+
var root = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
27+
var text = await document.GetTextAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
28+
var options = document.Project.Solution.Workspace.Options;
29+
var settings = SettingsHelper.GetStyleCopSettingsInCodeFix(document.Project.AnalyzerOptions, root.SyntaxTree, fixAllContext.CancellationToken);
3030

3131
ImmutableList<SyntaxNode> nodesToChange = ImmutableList.Create<SyntaxNode>();
3232

@@ -37,17 +37,29 @@ protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fi
3737
var syntaxNode = root.FindNode(location.SourceSpan);
3838
if (syntaxNode != null)
3939
{
40-
editor.TrackNode(syntaxNode);
4140
nodesToChange = nodesToChange.Add(syntaxNode);
4241
}
4342
}
4443

44+
var replacementTokens = new Dictionary<SyntaxToken, SyntaxToken>();
4545
foreach (var node in nodesToChange)
4646
{
47-
editor.ReplaceNode(node, node.WithLeadingTrivia(SyntaxFactory.ElasticCarriageReturnLineFeed));
47+
var firstToken = node.GetFirstToken();
48+
var endOfLine = FormattingHelper.GetEndOfLineForCodeFix(firstToken, text, options);
49+
50+
var firstTokenOnLine = IndentationHelper.GetFirstTokenOnTextLine(firstToken);
51+
var previousToken = firstToken.GetPreviousToken(includeZeroWidth: true);
52+
var replacementPreviousToken = previousToken.WithTrailingTrivia(previousToken.TrailingTrivia.WithoutTrailingWhitespace().Add(endOfLine));
53+
var indentSteps = IndentationHelper.GetIndentationSteps(settings.Indentation, firstTokenOnLine);
54+
var indentTrivia = IndentationHelper.GenerateWhitespaceTrivia(settings.Indentation, indentSteps);
55+
56+
replacementTokens.Add(previousToken, replacementPreviousToken);
57+
replacementTokens.Add(firstToken, firstToken.WithLeadingTrivia(indentTrivia));
4858
}
4959

50-
return editor.GetChangedRoot();
60+
return root.ReplaceTokens(
61+
replacementTokens.Keys,
62+
(originalToken, rewrittenToken) => replacementTokens[originalToken]);
5163
}
5264
}
5365
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/ReadabilityRules/SA1107UnitTests.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static void Foo(string a, string b)
4949
}
5050

5151
[Theory]
52-
[InlineData("\n", Skip = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3656")]
52+
[InlineData("\n")]
5353
[InlineData("\r\n")]
5454
public async Task TestWrongCodeAsync(string lineEnding)
5555
{
@@ -88,19 +88,18 @@ public static void Foo(string a, string b)
8888
{
8989
int i = 5;
9090
int j = 6, k = 3;
91-
if (true)
91+
if(true)
9292
{
9393
i++;
9494
}
9595
else
9696
{
9797
j++;
9898
}
99-
10099
Foo(""a"", ""b"");
101100
102101
Func<int, int, int> g = (c, d) => { c++;
103-
return c + d; };
102+
return c + d; };
104103
}
105104
}
106105
".ReplaceLineEndings(lineEnding);
@@ -157,17 +156,20 @@ static void Main(string[] args)
157156
}[|;|]
158157
}
159158
}
159+
";
160+
string fixedCode = @"
161+
class Program
162+
{
163+
static void Main(string[] args)
164+
{
165+
{
166+
}
167+
;
168+
}
169+
}
160170
";
161171

162-
await new CSharpTest
163-
{
164-
TestCode = testCode,
165-
FixedCode = testCode,
166-
167-
// A code fix is offered even though no changes are applied by it
168-
NumberOfIncrementalIterations = 1,
169-
NumberOfFixAllIterations = 1,
170-
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
172+
await VerifyCSharpFixAsync(testCode, DiagnosticResult.EmptyDiagnosticResults, fixedCode, CancellationToken.None).ConfigureAwait(false);
171173
}
172174
}
173175
}

0 commit comments

Comments
 (0)