Skip to content

Commit fb89c84

Browse files
committed
Add support for multi-file code fix verification.
1 parent c219678 commit fb89c84

4 files changed

Lines changed: 325 additions & 97 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.Test/Helpers/CodeFixVerifier.Helper.cs

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace TestHelper
1212
using Microsoft.CodeAnalysis.CodeActions;
1313
using Microsoft.CodeAnalysis.Formatting;
1414
using Microsoft.CodeAnalysis.Simplification;
15+
using Microsoft.CodeAnalysis.Text;
1516

1617
/// <summary>
1718
/// Diagnostic Producer class with extra methods dealing with applying code fixes.
@@ -23,16 +24,16 @@ public abstract partial class CodeFixVerifier : DiagnosticVerifier
2324
/// Apply the inputted <see cref="CodeAction"/> to the inputted document.
2425
/// Meant to be used to apply code fixes.
2526
/// </summary>
26-
/// <param name="document">The <see cref="Document"/> to apply the fix on</param>
27+
/// <param name="project">The <see cref="Project"/> to apply the fix on</param>
2728
/// <param name="codeAction">A <see cref="CodeAction"/> that will be applied to the
28-
/// <paramref name="document"/>.</param>
29+
/// <paramref name="project"/>.</param>
2930
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
30-
/// <returns>A <see cref="Document"/> with the changes from the <see cref="CodeAction"/>.</returns>
31-
private static async Task<Document> ApplyFixAsync(Document document, CodeAction codeAction, CancellationToken cancellationToken)
31+
/// <returns>A <see cref="Project"/> with the changes from the <see cref="CodeAction"/>.</returns>
32+
private static async Task<Project> ApplyFixAsync(Project project, CodeAction codeAction, CancellationToken cancellationToken)
3233
{
3334
var operations = await codeAction.GetOperationsAsync(cancellationToken).ConfigureAwait(false);
3435
var solution = operations.OfType<ApplyChangesOperation>().Single().ChangedSolution;
35-
return solution.GetDocument(document.Id);
36+
return solution.GetProject(project.Id);
3637
}
3738

3839
/// <summary>
@@ -75,13 +76,20 @@ private static IEnumerable<Diagnostic> GetNewDiagnostics(IEnumerable<Diagnostic>
7576
/// <summary>
7677
/// Get the existing compiler diagnostics on the input document.
7778
/// </summary>
78-
/// <param name="document">The <see cref="Document"/> to run the compiler diagnostic analyzers on.</param>
79+
/// <param name="project">The <see cref="Project"/> to run the compiler diagnostic analyzers on.</param>
7980
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that the task will observe.</param>
8081
/// <returns>The compiler diagnostics that were found in the code.</returns>
81-
private static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(Document document, CancellationToken cancellationToken)
82+
private static async Task<ImmutableArray<Diagnostic>> GetCompilerDiagnosticsAsync(Project project, CancellationToken cancellationToken)
8283
{
83-
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
84-
return semanticModel.GetDiagnostics(cancellationToken: cancellationToken);
84+
var allDiagnostics = ImmutableArray.Create<Diagnostic>();
85+
86+
foreach (var document in project.Documents)
87+
{
88+
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
89+
allDiagnostics = allDiagnostics.AddRange(semanticModel.GetDiagnostics(cancellationToken: cancellationToken));
90+
}
91+
92+
return allDiagnostics;
8593
}
8694

8795
/// <summary>
@@ -97,5 +105,49 @@ private static async Task<string> GetStringFromDocumentAsync(Document document,
97105
var sourceText = await formatted.GetTextAsync(cancellationToken).ConfigureAwait(false);
98106
return sourceText.ToString();
99107
}
108+
109+
/// <summary>
110+
/// Implements a workaround for issue #936, force re-parsing to get the same sort of syntax tree as the original document.
111+
/// </summary>
112+
/// <param name="project">The project to update.</param>
113+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
114+
/// <returns>The updated <see cref="Project"/>.</returns>
115+
private static async Task<Project> RecreateProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
116+
{
117+
foreach (var documentId in project.DocumentIds)
118+
{
119+
var document = project.GetDocument(documentId);
120+
document = await RecreateDocumentAsync(document, cancellationToken).ConfigureAwait(false);
121+
project = document.Project;
122+
}
123+
124+
return project;
125+
}
126+
127+
private static async Task<Document> RecreateDocumentAsync(Document document, CancellationToken cancellationToken)
128+
{
129+
var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
130+
newText = newText.WithChanges(new TextChange(new TextSpan(0, 0), " "));
131+
newText = newText.WithChanges(new TextChange(new TextSpan(0, 1), string.Empty));
132+
return document.WithText(newText);
133+
}
134+
135+
/// <summary>
136+
/// Formats the whitespace in all documents of the specified <see cref="Project"/>.
137+
/// </summary>
138+
/// <param name="project">The project to update.</param>
139+
/// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
140+
/// <returns>The updated <see cref="Project"/>.</returns>
141+
private static async Task<Project> ReformatProjectDocumentsAsync(Project project, CancellationToken cancellationToken)
142+
{
143+
foreach (var documentId in project.DocumentIds)
144+
{
145+
var document = project.GetDocument(documentId);
146+
document = await Formatter.FormatAsync(document, Formatter.Annotation, cancellationToken: cancellationToken).ConfigureAwait(false);
147+
project = document.Project;
148+
}
149+
150+
return project;
151+
}
100152
}
101153
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/FileMayOnlyContainTestBase.cs

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace StyleCop.Analyzers.Test.MaintainabilityRules
55
{
6+
using System.Linq;
67
using System.Threading;
78
using System.Threading.Tasks;
89
using TestHelper;
@@ -32,18 +33,30 @@ public async Task TestTwoElementsAsync()
3233
%1 Bar
3334
{
3435
}";
35-
var fixedCode = @"%1 Foo
36+
37+
var fixedCode = new[]
38+
{
39+
@"%1 Foo
3640
{
3741
}
38-
";
42+
",
43+
@"%1 Bar
44+
{
45+
}"
46+
};
3947

4048
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
4149

4250
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
43-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
51+
52+
foreach (var code in fixedCode)
53+
{
54+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
55+
}
56+
4457
if (this.SupportsCodeFix)
4558
{
46-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
59+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
4760
}
4861
}
4962

@@ -59,22 +72,38 @@ public async Task TestThreeElementsAsync()
5972
%1 FooBar
6073
{
6174
}";
62-
var fixedCode = @"%1 Foo
75+
76+
var fixedCode = new[]
77+
{
78+
@"%1 Foo
79+
{
80+
}
81+
",
82+
@"%1 Bar
6383
{
6484
}
65-
";
85+
",
86+
@"%1 FooBar
87+
{
88+
}"
89+
};
6690

6791
DiagnosticResult[] expected =
68-
{
69-
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
70-
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
71-
};
92+
{
93+
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
94+
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
95+
};
7296

7397
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
74-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
98+
99+
foreach (var code in fixedCode)
100+
{
101+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
102+
}
103+
75104
if (this.SupportsCodeFix)
76105
{
77-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
106+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
78107
}
79108
}
80109

@@ -89,18 +118,32 @@ public async Task TestRemoveWarningSuppressionAsync()
89118
#pragma warning disable SomeWarning
90119
#pragma warning restore SomeWarning
91120
}";
92-
var fixedCode = @"%1 Foo
121+
122+
var fixedCode = new[]
123+
{
124+
@"%1 Foo
93125
{
94126
}
95-
";
127+
",
128+
@"%1 Bar
129+
{
130+
#pragma warning disable SomeWarning
131+
#pragma warning restore SomeWarning
132+
}"
133+
};
96134

97135
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
98136

99137
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
100-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
138+
139+
foreach (var code in fixedCode)
140+
{
141+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
142+
}
143+
101144
if (this.SupportsCodeFix)
102145
{
103-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
146+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
104147
}
105148
}
106149

@@ -116,18 +159,30 @@ public async Task TestPreserveWarningSuppressionAsync()
116159
}";
117160

118161
// See https://github.com/dotnet/roslyn/issues/3999
119-
var fixedCode = @"%1 Foo
162+
var fixedCode = new[]
163+
{
164+
@"%1 Foo
120165
{
121166
}
122-
";
167+
",
168+
@"%1 Bar
169+
{
170+
#pragma warning disable SomeWarning
171+
}"
172+
};
123173

124174
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
125175

126176
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
127-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
177+
178+
foreach (var code in fixedCode)
179+
{
180+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
181+
}
182+
128183
if (this.SupportsCodeFix)
129184
{
130-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
185+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
131186
}
132187
}
133188

@@ -142,18 +197,32 @@ public async Task TestRemovePreprocessorDirectivesAsync()
142197
#if true
143198
#endif
144199
}";
145-
var fixedCode = @"%1 Foo
200+
201+
var fixedCode = new[]
202+
{
203+
@"%1 Foo
146204
{
147205
}
148-
";
206+
",
207+
@"%1 Bar
208+
{
209+
#if true
210+
#endif
211+
}"
212+
};
149213

150214
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2);
151215

152216
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
153-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
217+
218+
foreach (var code in fixedCode)
219+
{
220+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
221+
}
222+
154223
if (this.SupportsCodeFix)
155224
{
156-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
225+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
157226
}
158227
}
159228

@@ -170,21 +239,35 @@ public async Task TestPreservePreprocessorDirectivesAsync()
170239
}";
171240

172241
// See https://github.com/dotnet/roslyn/issues/3999
173-
var fixedCode = @"%1 Foo
242+
var fixedCode = new[]
243+
{
244+
@"%1 Foo
174245
{
175246
#if true
176247
}
177248
178249
#endif
179-
";
250+
",
251+
@"
252+
#if true
253+
%1 Bar
254+
{
255+
#endif
256+
}"
257+
};
180258

181259
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(5, this.Keyword.Length + 2);
182260

183261
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), expected, CancellationToken.None).ConfigureAwait(false);
184-
await this.VerifyCSharpDiagnosticAsync(fixedCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
262+
263+
foreach (var code in fixedCode)
264+
{
265+
await this.VerifyCSharpDiagnosticAsync(code.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
266+
}
267+
185268
if (this.SupportsCodeFix)
186269
{
187-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
270+
await this.VerifyCSharpFixAsync(new[] { testCode.Replace("%1", this.Keyword) }, fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray(), cancellationToken: CancellationToken.None).ConfigureAwait(false);
188271
}
189272
}
190273
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1402UnitTests.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,24 @@ public partial class Bar
4848
{
4949
5050
}";
51-
var fixedCode = @"public partial class Foo
51+
52+
var fixedCode = new[]
53+
{
54+
@"public partial class Foo
5255
{
5356
}
54-
";
57+
",
58+
@"public partial class Bar
59+
{
60+
61+
}"
62+
};
5563

5664
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(4, 22);
5765

5866
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
5967
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
60-
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
68+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
6169
}
6270

6371
[Fact]
@@ -70,15 +78,22 @@ public class Test0
7078
{
7179
}";
7280

73-
var fixedCode = @"public class Test0
81+
var fixedCode = new[]
82+
{
83+
@"public class Test0
7484
{
75-
}";
85+
}",
86+
@"public class Foo
87+
{
88+
}
89+
"
90+
};
7691

7792
DiagnosticResult expected = this.CSharpDiagnostic().WithLocation(1, 14);
7893

7994
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
8095
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
81-
await this.VerifyCSharpFixAsync(testCode, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
96+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
8297
}
8398

8499
[Fact]

0 commit comments

Comments
 (0)