@@ -43,54 +43,40 @@ public FormattingEngineImplementation(
43
43
_globalSemanticRules = globalSemanticRules . OrderBy ( r => r . Metadata . Order ) . Select ( r => r . Value ) ;
44
44
}
45
45
46
- public Task < bool > FormatSolutionAsync ( Solution solution , CancellationToken cancellationToken )
46
+ public Task FormatSolutionAsync ( Solution solution , CancellationToken cancellationToken )
47
47
{
48
48
var documentIds = solution . Projects . SelectMany ( x => x . DocumentIds ) . ToList ( ) ;
49
49
return FormatAsync ( solution . Workspace , documentIds , cancellationToken ) ;
50
50
}
51
51
52
- public Task < bool > FormatProjectAsync ( Project project , CancellationToken cancellationToken )
52
+ public Task FormatProjectAsync ( Project project , CancellationToken cancellationToken )
53
53
{
54
54
return FormatAsync ( project . Solution . Workspace , project . DocumentIds , cancellationToken ) ;
55
55
}
56
56
57
- private async Task < bool > FormatAsync ( Workspace workspace , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
57
+ private async Task FormatAsync ( Workspace workspace , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
58
58
{
59
- var solution = workspace . CurrentSolution ;
60
- var hasChanges = false ;
61
- var longRuleList = new List < Tuple < string , TimeSpan > > ( ) ;
62
-
63
- foreach ( var id in documentIds )
59
+ var originalSolution = workspace . CurrentSolution ;
60
+ var solution = originalSolution ;
61
+ solution = await RunSyntaxPass ( solution , documentIds , cancellationToken ) ;
62
+ solution = await RunLocalSemanticPass ( solution , documentIds , cancellationToken ) ;
63
+ solution = await RunGlobalSemanticPass ( solution , documentIds , cancellationToken ) ;
64
+
65
+ foreach ( var projectChange in solution . GetChanges ( originalSolution ) . GetProjectChanges ( ) )
64
66
{
65
- var document = solution . GetDocument ( id ) ;
66
- var shouldBeProcessed = await ShouldBeProcessedAsync ( document ) ;
67
- if ( ! shouldBeProcessed )
68
- {
69
- continue ;
70
- }
71
-
72
- longRuleList . Clear ( ) ;
73
- var watch = new Stopwatch ( ) ;
74
- watch . Start ( ) ;
75
- Console . Write ( "Processing document: " + document . Name ) ;
76
- var newDocument = await RewriteDocumentAsync ( document , longRuleList , cancellationToken ) ;
77
- hasChanges |= newDocument != document ;
78
- watch . Stop ( ) ;
79
- Console . WriteLine ( " {0} seconds" , watch . Elapsed . TotalSeconds ) ;
80
- foreach ( var tuple in longRuleList )
67
+ foreach ( var documentId in projectChange . GetChangedDocuments ( ) )
81
68
{
82
- Console . WriteLine ( "\t {0} {1} seconds" , tuple . Item1 , tuple . Item2 . TotalSeconds ) ;
69
+ var document = solution . GetDocument ( documentId ) ;
70
+ var sourceText = await document . GetTextAsync ( cancellationToken ) ;
71
+ using ( var file = File . Open ( document . FilePath , FileMode . Truncate , FileAccess . Write ) )
72
+ {
73
+ using ( var writer = new StreamWriter ( file , sourceText . Encoding ) )
74
+ {
75
+ sourceText . Write ( writer , cancellationToken ) ;
76
+ }
77
+ }
83
78
}
84
-
85
- solution = newDocument . Project . Solution ;
86
- }
87
-
88
- if ( workspace . TryApplyChanges ( solution ) )
89
- {
90
- Console . WriteLine ( "Solution changes committed" ) ;
91
79
}
92
-
93
- return hasChanges ;
94
80
}
95
81
96
82
private async Task < bool > ShouldBeProcessedAsync ( Document document )
@@ -105,31 +91,24 @@ private async Task<bool> ShouldBeProcessedAsync(Document document)
105
91
return true ;
106
92
}
107
93
108
- private async Task < Document > RewriteDocumentAsync ( Document document , List < Tuple < string , TimeSpan > > longRuleList , CancellationToken cancellationToken )
94
+ private async Task < SyntaxNode > GetSyntaxRootAndFilter ( Document document , CancellationToken cancellationToken )
109
95
{
110
- var docText = await document . GetTextAsync ( ) ;
111
- var originalEncoding = docText . Encoding ;
112
- var watch = new Stopwatch ( ) ;
113
- foreach ( var rule in _rules )
96
+ if ( ! await ShouldBeProcessedAsync ( document ) )
114
97
{
115
- watch . Start ( ) ;
116
- document = await rule . ProcessAsync ( document , cancellationToken ) ;
117
- watch . Stop ( ) ;
118
- var timeSpan = watch . Elapsed ;
119
- if ( timeSpan . TotalSeconds > 1.0 )
120
- {
121
- longRuleList . Add ( Tuple . Create ( rule . GetType ( ) . Name , timeSpan ) ) ;
122
- }
123
-
124
- watch . Reset ( ) ;
98
+ return null ;
125
99
}
126
100
127
- return await ChangeEncoding ( document , originalEncoding ) ;
101
+ return await document . GetSyntaxRootAsync ( cancellationToken ) ;
128
102
}
129
103
130
- private void StartDocument ( Document document )
104
+ private void StartDocument ( Document document , int depth = 1 )
131
105
{
132
- Console . Write ( "\t Processing {0}" , document . Name ) ;
106
+ for ( int i = 0 ; i < depth ; i ++ )
107
+ {
108
+ Console . Write ( "\t " ) ;
109
+ }
110
+
111
+ Console . Write ( "Processing {0}" , document . Name ) ;
133
112
_watch . Restart ( ) ;
134
113
}
135
114
@@ -138,91 +117,126 @@ private void EndDocument()
138
117
_watch . Stop ( ) ;
139
118
if ( _verbose && _watch . Elapsed . TotalSeconds > 1 )
140
119
{
141
-
120
+ Console . WriteLine ( " {0} seconds" , _watch . Elapsed . TotalSeconds ) ;
142
121
}
122
+
123
+ Console . WriteLine ( ) ;
143
124
}
144
125
145
- private Task < Solution > FormatDocumentsSyntaxPass ( Solution originalSolution , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
126
+ /// <summary>
127
+ /// Semantics is not involved in this pass at all. It is just a straight modification of the
128
+ /// parse tree so there are no issues about ensuring the version of <see cref="SemanticModel"/> and
129
+ /// the <see cref="SyntaxNode"/> line up. Hence we do this by iteraning every <see cref="Document"/>
130
+ /// and processing all rules against them at once
131
+ /// </summary>
132
+ private async Task < Solution > RunSyntaxPass ( Solution originalSolution , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
146
133
{
147
134
Console . WriteLine ( "Syntax Pass" ) ;
148
135
149
136
var currentSolution = originalSolution ;
150
137
foreach ( var documentId in documentIds )
151
138
{
152
139
var document = originalSolution . GetDocument ( documentId ) ;
153
-
154
- Console . Write ( "\t Processing {0}" , document . Name ) ;
155
-
156
- watch . Restart ( ) ;
157
- var newRoot = await documentFunc ( document ) ;
158
- watch . Stop ( ) ;
159
-
160
- if ( _verbose && watch . Elapsed . TotalSeconds > 1 )
140
+ var syntaxRoot = await GetSyntaxRootAndFilter ( document , cancellationToken ) ;
141
+ if ( syntaxRoot == null )
161
142
{
162
- Console . Write ( " {0} seconds" , watch . Elapsed . TotalSeconds ) ;
143
+ continue ;
163
144
}
164
- Console . WriteLine ( ) ;
165
145
166
- if ( newRoot != null )
146
+ StartDocument ( document ) ;
147
+ var newRoot = RunSyntaxPass ( syntaxRoot ) ;
148
+ EndDocument ( ) ;
149
+
150
+ if ( newRoot != syntaxRoot )
167
151
{
168
- currentSolution = currentSolution . WithDocumentSyntaxRoot ( documentId , newRoot ) ;
152
+ currentSolution = currentSolution . WithDocumentSyntaxRoot ( document . Id , newRoot ) ;
169
153
}
170
154
}
171
155
172
156
return currentSolution ;
173
157
}
174
158
175
- private async Task < Solution > FormatDocumentsLocalSemanticPass ( Solution originalSolution , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
159
+ private SyntaxNode RunSyntaxPass ( SyntaxNode root )
176
160
{
177
- Console . WriteLine ( "Local Semantic Pass" ) ;
178
- Func < Document , Task < SyntaxTree > documentFunc = async ( documentFunc ) =>
179
- {
180
- var syntaxRoot = await document . GetSyntaxRootAsync ( cancellationToken ) ;
181
- if ( syntaxRoot == null )
182
- {
183
- return null ;
184
- }
185
-
186
- return FormatLocalSemantic ( document , syntaxRoot ) ;
187
- } ;
161
+ foreach ( var rule in _syntaxRules )
162
+ {
163
+ root = rule . Process ( root ) ;
164
+ }
188
165
189
- return FormatDocumentsCore ( originalSolution , documentIds , documentFunc , cancellationToken ) ;
166
+ return root ;
190
167
}
191
168
192
- private async Task < Solution > FormatDocumentsCore (
193
- Solution originalSolution ,
194
- IReadOnlyList < DocumentId > documentIds ,
195
- Func < Document , Task < SyntaxTree > documentFunc ,
196
- CancellationToken cancellationToken )
169
+ private async Task < Solution > RunLocalSemanticPass ( Solution solution , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
197
170
{
171
+ Console . WriteLine ( "Local Semantic Pass" ) ;
172
+ foreach ( var localSemanticRule in _localSemanticRules )
173
+ {
174
+ solution = await RunLocalSemanticPass ( solution , documentIds , localSemanticRule , cancellationToken ) ;
175
+ }
176
+
177
+ return solution ;
198
178
}
199
179
200
- private Task < SyntaxTree > FormatSyntaxTree ( Document document , SyntaxNode syntaxRoot )
180
+ private async Task < Solution > RunLocalSemanticPass ( Solution originalSolution , IReadOnlyList < DocumentId > documentIds , ILocalSemanticFormattingRule localSemanticRule , CancellationToken cancellationToken )
201
181
{
202
- foreach ( var syntaxRule in _syntaxRules )
182
+ Console . WriteLine ( "\t {0}" , localSemanticRule . GetType ( ) . Name ) ;
183
+ var currentSolution = originalSolution ;
184
+ foreach ( var documentId in documentIds )
203
185
{
204
- syntaxRoot = syntaxRule . Process ( syntaxRoot ) ;
186
+ var document = originalSolution . GetDocument ( documentId ) ;
187
+ var syntaxRoot = await GetSyntaxRootAndFilter ( document , cancellationToken ) ;
188
+ if ( syntaxRoot == null )
189
+ {
190
+ continue ;
191
+ }
192
+
193
+ StartDocument ( document , depth : 2 ) ;
194
+ var newRoot = await localSemanticRule . ProcessAsync ( document , syntaxRoot , cancellationToken ) ;
195
+ EndDocument ( ) ;
196
+
197
+ if ( syntaxRoot != newRoot )
198
+ {
199
+ currentSolution = currentSolution . WithDocumentSyntaxRoot ( documentId , newRoot ) ;
200
+ }
205
201
}
206
202
207
- return Task . FromResult ( root ) ;
203
+ return currentSolution ;
208
204
}
209
205
210
- private async Task < SyntaxTree > FormatLocalSemantic ( Document originalDocument , SyntaxNode originalSyntaxRoot )
206
+ private async Task < Solution > RunGlobalSemanticPass ( Solution solution , IReadOnlyList < DocumentId > documentIds , CancellationToken cancellationToken )
211
207
{
212
- var currentSyntaxRoot = originalSyntaxRoot ;
213
- foreach ( var localSemanticRule in _localSemanticRules )
208
+ Console . WriteLine ( "Global Semantic Pass" ) ;
209
+ foreach ( var globalSemanticRule in _globalSemanticRules )
214
210
{
215
- currentSyntaxRoot = await localSemanticRule . ProcessAsync ( originalDocument , originalSyntaxRoot , currentSyntaxRoot )
211
+ solution = await RunGlobalSemanticPass ( solution , documentIds , globalSemanticRule , cancellationToken ) ;
216
212
}
217
213
218
- return currentSyntaxRoot ;
214
+ return solution ;
219
215
}
220
216
221
- private async Task < Document > ChangeEncoding ( Document document , Encoding encoding )
217
+ private async Task < Solution > RunGlobalSemanticPass ( Solution solution , IReadOnlyList < DocumentId > documentIds , IGlobalSemanticFormattingRule globalSemanticRule , CancellationToken cancellationToken )
222
218
{
223
- var text = await document . GetTextAsync ( ) ;
224
- var newText = SourceText . From ( text . ToString ( ) , encoding ) ;
225
- return document . WithText ( newText ) ;
219
+ Console . WriteLine ( "\t {0}" , globalSemanticRule . GetType ( ) . Name ) ;
220
+ foreach ( var documentId in documentIds )
221
+ {
222
+ var document = solution . GetDocument ( documentId ) ;
223
+ var syntaxRoot = await GetSyntaxRootAndFilter ( document , cancellationToken ) ;
224
+ if ( syntaxRoot == null )
225
+ {
226
+ continue ;
227
+ }
228
+
229
+ StartDocument ( document , depth : 2 ) ;
230
+ var newRoot = await globalSemanticRule . ProcessAsync ( document , syntaxRoot , cancellationToken ) ;
231
+ EndDocument ( ) ;
232
+
233
+ if ( syntaxRoot != newRoot )
234
+ {
235
+ solution = solution . WithDocumentSyntaxRoot ( documentId , newRoot ) ;
236
+ }
237
+ }
238
+
239
+ return solution ;
226
240
}
227
241
}
228
242
}
0 commit comments