Skip to content

Commit 088e573

Browse files
committed
Merge pull request #2140 from otac0n/multi-file-code-fix-verification
Multi-file code fix verification
2 parents f044621 + 4170c61 commit 088e573

4 files changed

Lines changed: 290 additions & 104 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: 115 additions & 34 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;
@@ -20,7 +21,10 @@ public async Task TestOneElementAsync()
2021
var testCode = @"%1 Foo
2122
{
2223
}";
23-
await this.VerifyCSharpDiagnosticAsync(testCode.Replace("%1", this.Keyword), EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
24+
25+
testCode = testCode.Replace("%1", this.Keyword);
26+
27+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
2428
}
2529

2630
[Fact]
@@ -32,18 +36,29 @@ public async Task TestTwoElementsAsync()
3236
%1 Bar
3337
{
3438
}";
35-
var fixedCode = @"%1 Foo
39+
40+
var fixedCode = new[]
41+
{
42+
@"%1 Foo
3643
{
3744
}
38-
";
45+
",
46+
@"%1 Bar
47+
{
48+
}"
49+
};
50+
51+
testCode = testCode.Replace("%1", this.Keyword);
52+
fixedCode = fixedCode.Select(c => c.Replace("%1", this.Keyword)).ToArray();
3953

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

42-
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);
56+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
57+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
58+
4459
if (this.SupportsCodeFix)
4560
{
46-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
61+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
4762
}
4863
}
4964

@@ -59,22 +74,37 @@ public async Task TestThreeElementsAsync()
5974
%1 FooBar
6075
{
6176
}";
62-
var fixedCode = @"%1 Foo
77+
78+
var fixedCode = new[]
79+
{
80+
@"%1 Foo
81+
{
82+
}
83+
",
84+
@"%1 Bar
6385
{
6486
}
65-
";
87+
",
88+
@"%1 FooBar
89+
{
90+
}"
91+
};
92+
93+
testCode = testCode.Replace("%1", this.Keyword);
94+
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();
6695

6796
DiagnosticResult[] expected =
68-
{
69-
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
70-
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
71-
};
97+
{
98+
this.CSharpDiagnostic().WithLocation(4, this.Keyword.Length + 2),
99+
this.CSharpDiagnostic().WithLocation(7, this.Keyword.Length + 2)
100+
};
101+
102+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
103+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
72104

73-
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);
75105
if (this.SupportsCodeFix)
76106
{
77-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
107+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
78108
}
79109
}
80110

@@ -89,18 +119,31 @@ public async Task TestRemoveWarningSuppressionAsync()
89119
#pragma warning disable SomeWarning
90120
#pragma warning restore SomeWarning
91121
}";
92-
var fixedCode = @"%1 Foo
122+
123+
var fixedCode = new[]
124+
{
125+
@"%1 Foo
93126
{
94127
}
95-
";
128+
",
129+
@"%1 Bar
130+
{
131+
#pragma warning disable SomeWarning
132+
#pragma warning restore SomeWarning
133+
}"
134+
};
135+
136+
testCode = testCode.Replace("%1", this.Keyword);
137+
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();
96138

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

99-
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);
141+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
142+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
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 }, fixedCode, 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,31 @@ 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+
};
213+
214+
testCode = testCode.Replace("%1", this.Keyword);
215+
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();
149216

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

152-
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);
219+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
220+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
221+
154222
if (this.SupportsCodeFix)
155223
{
156-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
224+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
157225
}
158226
}
159227

@@ -170,21 +238,34 @@ public async Task TestPreservePreprocessorDirectivesAsync()
170238
}";
171239

172240
// See https://github.com/dotnet/roslyn/issues/3999
173-
var fixedCode = @"%1 Foo
241+
var fixedCode = new[]
242+
{
243+
@"%1 Foo
174244
{
175245
#if true
176246
}
177247
178248
#endif
179-
";
249+
",
250+
@"
251+
#if true
252+
%1 Bar
253+
{
254+
#endif
255+
}"
256+
};
257+
258+
testCode = testCode.Replace("%1", this.Keyword);
259+
fixedCode = fixedCode.Select(code => code.Replace("%1", this.Keyword)).ToArray();
180260

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

183-
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);
263+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
264+
await this.VerifyCSharpDiagnosticAsync(fixedCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
265+
185266
if (this.SupportsCodeFix)
186267
{
187-
await this.VerifyCSharpFixAsync(testCode.Replace("%1", this.Keyword), fixedCode.Replace("%1", this.Keyword), cancellationToken: CancellationToken.None).ConfigureAwait(false);
268+
await this.VerifyCSharpFixAsync(new[] { testCode }, fixedCode, cancellationToken: CancellationToken.None).ConfigureAwait(false);
188269
}
189270
}
190271
}

0 commit comments

Comments
 (0)