11
11
using Microsoft . CodeAnalysis ;
12
12
using Microsoft . CodeAnalysis . CodeActions ;
13
13
using Microsoft . CodeAnalysis . CodeCleanup ;
14
- using Microsoft . CodeAnalysis . Editor ;
15
14
using Microsoft . CodeAnalysis . Editor . Shared . Extensions ;
16
15
using Microsoft . CodeAnalysis . Editor . Shared . Utilities ;
17
- using Microsoft . CodeAnalysis . Formatting ;
18
16
using Microsoft . CodeAnalysis . Host ;
19
17
using Microsoft . CodeAnalysis . Options ;
20
- using Microsoft . CodeAnalysis . PooledObjects ;
21
18
using Microsoft . CodeAnalysis . Progress ;
22
19
using Microsoft . CodeAnalysis . Shared . Extensions ;
23
20
using Microsoft . CodeAnalysis . Shared . Utilities ;
@@ -69,7 +66,13 @@ private async Task<bool> FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h
69
66
var hierarchy = hierarchyContent . Hierarchy ;
70
67
if ( hierarchy == null )
71
68
{
72
- return await FixSolutionAsync ( _workspace . CurrentSolution , context ) . ConfigureAwait ( true ) ;
69
+ var solution = _workspace . CurrentSolution ;
70
+ return await FixAsync (
71
+ _workspace ,
72
+ // Just defer to FixProjectsAsync, passing in all fixable projects in the solution.
73
+ ( progress , cancellationToken ) => FixProjectsAsync (
74
+ _globalOptions , solution , solution . Projects . Where ( p => p . SupportsCompilation ) . ToImmutableArray ( ) , context . EnabledFixIds , progress , cancellationToken ) ,
75
+ context ) . ConfigureAwait ( false ) ;
73
76
}
74
77
75
78
// Map the hierarchy to a ProjectId. For hierarchies mapping to multitargeted projects, we first try to
@@ -90,9 +93,7 @@ private async Task<bool> FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h
90
93
{
91
94
var projectHierarchyItem = _vsHierarchyItemManager . GetHierarchyItem ( hierarchyContent . Hierarchy , ( uint ) VSConstants . VSITEMID . Root ) ;
92
95
if ( ! hierarchyToProjectMap . TryGetProjectId ( projectHierarchyItem , targetFrameworkMoniker : null , out projectId ) )
93
- {
94
96
return false ;
95
- }
96
97
}
97
98
98
99
var itemId = hierarchyContent . ItemId ;
@@ -101,12 +102,15 @@ private async Task<bool> FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h
101
102
await TaskScheduler . Default ;
102
103
103
104
var project = _workspace . CurrentSolution . GetProject ( projectId ) ;
104
- if ( project == null )
105
- {
105
+ if ( project == null || ! project . SupportsCompilation )
106
106
return false ;
107
- }
108
107
109
- return await FixProjectAsync ( project , context ) . ConfigureAwait ( true ) ;
108
+ return await FixAsync (
109
+ _workspace ,
110
+ // Just defer to FixProjectsAsync, passing in this single project to fix.
111
+ ( progress , cancellationToken ) => FixProjectsAsync (
112
+ _globalOptions , project . Solution , [ project ] , context . EnabledFixIds , progress , cancellationToken ) ,
113
+ context ) . ConfigureAwait ( false ) ;
110
114
}
111
115
else if ( hierarchy . GetCanonicalName ( itemId , out var path ) == 0 )
112
116
{
@@ -126,54 +130,25 @@ private async Task<bool> FixHierarchyContentAsync(IVsHierarchyCodeCleanupScope h
126
130
var documentIds = solution . GetDocumentIdsWithFilePath ( path ) ;
127
131
var documentId = documentIds . FirstOrDefault ( id => id . ProjectId == projectId ) ;
128
132
if ( documentId is null )
129
- {
130
133
return false ;
131
- }
132
134
133
135
var document = solution . GetRequiredDocument ( documentId ) ;
134
136
var options = _globalOptions . GetCodeActionOptions ( document . Project . Services ) ;
135
- return await FixDocumentAsync ( document , options , context ) . ConfigureAwait ( true ) ;
137
+
138
+ return await FixAsync (
139
+ _workspace ,
140
+ async ( progress , cancellationToken ) =>
141
+ {
142
+ var newDocument = await FixDocumentAsync ( document , context . EnabledFixIds , progress , options , cancellationToken ) . ConfigureAwait ( true ) ;
143
+ return newDocument . Project . Solution ;
144
+ } ,
145
+ context ) . ConfigureAwait ( false ) ;
136
146
}
137
147
}
138
148
139
149
return false ;
140
150
}
141
151
142
- private Task < bool > FixSolutionAsync ( Solution solution , ICodeCleanUpExecutionContext context )
143
- {
144
- return FixAsync ( _workspace , ApplyFixAsync , context ) ;
145
-
146
- // Local function
147
- Task < Solution > ApplyFixAsync ( IProgress < CodeAnalysisProgress > progress , CancellationToken cancellationToken )
148
- {
149
- return FixSolutionAsync ( solution , context . EnabledFixIds , progress , cancellationToken ) ;
150
- }
151
- }
152
-
153
- private Task < bool > FixProjectAsync ( Project project , ICodeCleanUpExecutionContext context )
154
- {
155
- return FixAsync ( _workspace , ApplyFixAsync , context ) ;
156
-
157
- // Local function
158
- async Task < Solution > ApplyFixAsync ( IProgress < CodeAnalysisProgress > progress , CancellationToken cancellationToken )
159
- {
160
- var newProject = await FixProjectAsync ( project , context . EnabledFixIds , progress , addProgressItemsForDocuments : true , cancellationToken ) . ConfigureAwait ( true ) ;
161
- return newProject . Solution ;
162
- }
163
- }
164
-
165
- private Task < bool > FixDocumentAsync ( Document document , CodeActionOptions options , ICodeCleanUpExecutionContext context )
166
- {
167
- return FixAsync ( document . Project . Solution . Workspace , ApplyFixAsync , context ) ;
168
-
169
- // Local function
170
- async Task < Solution > ApplyFixAsync ( IProgress < CodeAnalysisProgress > progress , CancellationToken cancellationToken )
171
- {
172
- var newDocument = await FixDocumentAsync ( document , context . EnabledFixIds , progress , options , cancellationToken ) . ConfigureAwait ( true ) ;
173
- return newDocument . Project . Solution ;
174
- }
175
- }
176
-
177
152
private Task < bool > FixTextBufferAsync ( TextBufferCodeCleanUpScope textBufferScope , ICodeCleanUpExecutionContext context )
178
153
{
179
154
var buffer = textBufferScope . SubjectBuffer ;
@@ -215,9 +190,7 @@ private async Task<bool> FixAsync(
215
190
{
216
191
var workspaceStatusService = workspace . Services . GetService < IWorkspaceStatusService > ( ) ;
217
192
if ( workspaceStatusService != null )
218
- {
219
193
await workspaceStatusService . WaitUntilFullyLoadedAsync ( context . OperationContext . UserCancellationToken ) . ConfigureAwait ( true ) ;
220
- }
221
194
}
222
195
223
196
using ( var scope = context . OperationContext . AddScope ( allowCancellation : true , description : EditorFeaturesResources . Applying_changes ) )
@@ -233,87 +206,68 @@ private async Task<bool> FixAsync(
233
206
}
234
207
}
235
208
236
- private async Task < Solution > FixSolutionAsync (
209
+ private static async Task < Solution > FixProjectsAsync (
210
+ IGlobalOptionService globalOptions ,
237
211
Solution solution ,
212
+ ImmutableArray < Project > projects ,
238
213
FixIdContainer enabledFixIds ,
239
214
IProgress < CodeAnalysisProgress > progressTracker ,
240
215
CancellationToken cancellationToken )
241
216
{
242
- // Prepopulate the solution progress tracker with the total number of documents to process
243
- foreach ( var projectId in solution . ProjectIds )
244
- {
245
- var project = solution . GetRequiredProject ( projectId ) ;
246
- if ( ! CanCleanupProject ( project ) )
247
- {
248
- continue ;
249
- }
250
-
251
- progressTracker . AddItems ( project . DocumentIds . Count ) ;
252
- }
253
-
254
- foreach ( var projectId in solution . ProjectIds )
255
- {
256
- cancellationToken . ThrowIfCancellationRequested ( ) ;
257
-
258
- var project = solution . GetRequiredProject ( projectId ) ;
259
- var newProject = await FixProjectAsync ( project , enabledFixIds , progressTracker , addProgressItemsForDocuments : false , cancellationToken ) . ConfigureAwait ( false ) ;
260
- solution = newProject . Solution ;
261
- }
262
-
263
- return solution ;
264
- }
265
-
266
- private async Task < Project > FixProjectAsync (
267
- Project project ,
268
- FixIdContainer enabledFixIds ,
269
- IProgress < CodeAnalysisProgress > progressTracker ,
270
- bool addProgressItemsForDocuments ,
271
- CancellationToken cancellationToken )
272
- {
273
- if ( ! CanCleanupProject ( project ) )
274
- {
275
- return project ;
276
- }
277
-
278
- if ( addProgressItemsForDocuments )
279
- {
280
- progressTracker . AddItems ( project . DocumentIds . Count ) ;
281
- }
282
-
283
- var ideOptions = _globalOptions . GetCodeActionOptions ( project . Services ) ;
284
-
285
- foreach ( var documentId in project . DocumentIds )
286
- {
287
- cancellationToken . ThrowIfCancellationRequested ( ) ;
288
-
289
- var document = project . GetRequiredDocument ( documentId ) ;
290
- progressTracker . Report ( CodeAnalysisProgress . Description ( document . Name ) ) ;
217
+ // Add an item for each document in all the projects we're processing.
218
+ progressTracker . AddItems ( projects . Sum ( static p => p . DocumentIds . Count ) ) ;
291
219
292
- // FixDocumentAsync reports progress within a document, but we only want to report progress at the
293
- // project granularity. So we pass CodeAnalysisProgress.None here so that inner progress updates don't
294
- // affect us.
295
- var fixedDocument = await FixDocumentAsync ( document , enabledFixIds , CodeAnalysisProgress . None , ideOptions , cancellationToken ) . ConfigureAwait ( false ) ;
296
- project = fixedDocument . Project ;
297
- progressTracker . ItemCompleted ( ) ;
298
- }
299
-
300
- return project ;
220
+ // Run in parallel across all projects.
221
+ return await ProducerConsumer < ( DocumentId documentId , SyntaxNode newRoot ) > . RunParallelAsync (
222
+ source : projects ,
223
+ produceItems : static async ( project , callback , args , cancellationToken ) =>
224
+ {
225
+ Contract . ThrowIfFalse ( project . SupportsCompilation ) ;
226
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
227
+
228
+ var ideOptions = args . globalOptions . GetCodeActionOptions ( project . Services ) ;
229
+
230
+ // And for each project, process all the documents in parallel.
231
+ await RoslynParallel . ForEachAsync (
232
+ source : project . Documents ,
233
+ cancellationToken ,
234
+ async ( document , cancellationToken ) =>
235
+ {
236
+ using var _ = args . progressTracker . ItemCompletedScope ( ) ;
237
+
238
+ // FixDocumentAsync reports progress within a document, but we only want to report progress at
239
+ // the document granularity. So we pass CodeAnalysisProgress.None here so that inner progress
240
+ // updates don't affect us.
241
+ var fixedDocument = await FixDocumentAsync ( document , args . enabledFixIds , CodeAnalysisProgress . None , ideOptions , cancellationToken ) . ConfigureAwait ( false ) ;
242
+ if ( fixedDocument == document )
243
+ return ;
244
+
245
+ callback ( ( document . Id , await fixedDocument . GetRequiredSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ) ) ;
246
+ } ) . ConfigureAwait ( false ) ;
247
+ } ,
248
+ consumeItems : static async ( stream , args , cancellationToken ) =>
249
+ {
250
+ // Now consume the changed documents, applying their new roots to the solution.
251
+ var currentSolution = args . solution ;
252
+ await foreach ( var ( documentId , newRoot ) in stream )
253
+ currentSolution = currentSolution . WithDocumentSyntaxRoot ( documentId , newRoot ) ;
254
+
255
+ return currentSolution ;
256
+ } ,
257
+ args : ( globalOptions , solution , enabledFixIds , progressTracker ) ,
258
+ cancellationToken ) . ConfigureAwait ( false ) ;
301
259
}
302
260
303
- private static bool CanCleanupProject ( Project project )
304
- => project . Services . GetService < ICodeCleanupService > ( ) != null ;
305
-
306
261
private static async Task < Document > FixDocumentAsync (
307
262
Document document ,
308
263
FixIdContainer enabledFixIds ,
309
264
IProgress < CodeAnalysisProgress > progressTracker ,
310
265
CodeActionOptions ideOptions ,
311
266
CancellationToken cancellationToken )
312
267
{
268
+ cancellationToken . ThrowIfCancellationRequested ( ) ;
313
269
if ( document . IsGeneratedCode ( cancellationToken ) )
314
- {
315
270
return document ;
316
- }
317
271
318
272
var codeCleanupService = document . GetRequiredLanguageService < ICodeCleanupService > ( ) ;
319
273
0 commit comments