22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
44using System ;
5- using System . Collections . Generic ;
65using System . Collections . Immutable ;
6+ using System . Diagnostics ;
77using System . IO ;
88using System . Linq ;
99using Microsoft . AspNetCore . Razor . Language ;
10+ using Microsoft . AspNetCore . Razor . PooledObjects ;
1011using Microsoft . CodeAnalysis ;
1112using Microsoft . CodeAnalysis . CSharp ;
13+ using Microsoft . CodeAnalysis . CSharp . Syntax ;
1214
1315namespace Microsoft . NET . Sdk . Razor . SourceGenerators
1416{
@@ -66,69 +68,82 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
6668 var generatedDeclarationCode = componentFiles
6769 . Combine ( importFiles . Collect ( ) )
6870 . Combine ( razorSourceGeneratorOptions )
71+ . WithLambdaComparer ( ( old , @new ) => ( old . Right . Equals ( @new . Right ) && old . Left . Left . Equals ( @new . Left . Left ) && old . Left . Right . SequenceEqual ( @new . Left . Right ) ) , ( a ) => a . GetHashCode ( ) )
6972 . Select ( static ( pair , _ ) =>
7073 {
7174
7275 var ( ( sourceItem , importFiles ) , razorSourceGeneratorOptions ) = pair ;
73- RazorSourceGeneratorEventSource . Log . GenerateDeclarationCodeStart ( sourceItem . FilePath ) ;
76+ RazorSourceGeneratorEventSource . Log . GenerateDeclarationCodeStart ( sourceItem . RelativePhysicalPath ) ;
7477
7578 var projectEngine = GetDeclarationProjectEngine ( sourceItem , importFiles , razorSourceGeneratorOptions ) ;
7679
7780 var codeGen = projectEngine . Process ( sourceItem ) ;
7881
7982 var result = codeGen . GetCSharpDocument ( ) . GeneratedCode ;
8083
81- RazorSourceGeneratorEventSource . Log . GenerateDeclarationCodeStop ( sourceItem . FilePath ) ;
84+ RazorSourceGeneratorEventSource . Log . GenerateDeclarationCodeStop ( sourceItem . RelativePhysicalPath ) ;
8285
83- return result ;
86+ return ( result , sourceItem . RelativePhysicalPath ) ;
8487 } ) ;
8588
8689 var generatedDeclarationSyntaxTrees = generatedDeclarationCode
8790 . Combine ( parseOptions )
88- . Select ( static ( pair , _ ) =>
91+ . Select ( static ( pair , ct ) =>
8992 {
90- var ( generatedDeclarationCode , parseOptions ) = pair ;
91- return CSharpSyntaxTree . ParseText ( generatedDeclarationCode , ( CSharpParseOptions ) parseOptions ) ;
93+ var ( ( generatedDeclarationCode , filePath ) , parseOptions ) = pair ;
94+ return CSharpSyntaxTree . ParseText ( generatedDeclarationCode , ( CSharpParseOptions ) parseOptions , filePath , cancellationToken : ct ) ;
9295 } ) ;
9396
94- var tagHelpersFromCompilation = compilation
95- . Combine ( generatedDeclarationSyntaxTrees . Collect ( ) )
97+ var tagHelpersFromComponents = generatedDeclarationSyntaxTrees
98+ . Combine ( compilation )
9699 . Combine ( razorSourceGeneratorOptions )
97- . Select ( static ( pair , _ ) =>
100+ . SelectMany ( static ( pair , ct ) =>
98101 {
99- RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromCompilationStart ( ) ;
100102
101- var ( ( compilation , generatedDeclarationSyntaxTrees ) , razorSourceGeneratorOptions ) = pair ;
103+ var ( ( generatedDeclarationSyntaxTree , compilation ) , razorSourceGeneratorOptions ) = pair ;
104+ RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromComponentStart ( generatedDeclarationSyntaxTree . FilePath ) ;
102105
103106 var tagHelperFeature = new StaticCompilationTagHelperFeature ( ) ;
104107 var discoveryProjectEngine = GetDiscoveryProjectEngine ( compilation . References . ToImmutableArray ( ) , tagHelperFeature ) ;
105108
106- var compilationWithDeclarations = compilation . AddSyntaxTrees ( generatedDeclarationSyntaxTrees ) ;
109+ var compilationWithDeclarations = compilation . AddSyntaxTrees ( generatedDeclarationSyntaxTree ) ;
110+
111+ // try and find the specific root class this component is declaring, falling back to the assembly if for any reason the code is not in the shape we expect
112+ ISymbol targetSymbol = compilationWithDeclarations . Assembly ;
113+ var root = generatedDeclarationSyntaxTree . GetRoot ( ct ) ;
114+ if ( root is CompilationUnitSyntax { Members : [ NamespaceDeclarationSyntax { Members : [ ClassDeclarationSyntax classSyntax , ..] } , ..] } )
115+ {
116+ var declaredClass = compilationWithDeclarations . GetSemanticModel ( generatedDeclarationSyntaxTree ) . GetDeclaredSymbol ( classSyntax , ct ) ;
117+ Debug . Assert ( declaredClass is null || declaredClass is { AllInterfaces : [ { Name : "IComponent" } , ..] } ) ;
118+ targetSymbol = declaredClass ?? targetSymbol ;
119+ }
107120
108121 tagHelperFeature . Compilation = compilationWithDeclarations ;
109- tagHelperFeature . TargetSymbol = compilationWithDeclarations . Assembly ;
122+ tagHelperFeature . TargetSymbol = targetSymbol ;
110123
111- var result = ( IList < TagHelperDescriptor > ) tagHelperFeature . GetDescriptors ( ) ;
112- RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromCompilationStop ( ) ;
124+ var result = tagHelperFeature . GetDescriptors ( ) ;
125+ RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromComponentStop ( generatedDeclarationSyntaxTree . FilePath ) ;
113126 return result ;
114- } )
115- . WithLambdaComparer ( static ( a , b ) =>
127+ } ) ;
128+
129+ var tagHelpersFromCompilation = compilation
130+ . Combine ( razorSourceGeneratorOptions )
131+ . Select ( static ( pair , _ ) =>
116132 {
117- if ( a . Count != b . Count )
118- {
119- return false ;
120- }
133+ RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromCompilationStart ( ) ;
121134
122- for ( var i = 0 ; i < a . Count ; i ++ )
123- {
124- if ( ! a [ i ] . Equals ( b [ i ] ) )
125- {
126- return false ;
127- }
128- }
135+ var ( compilation , razorSourceGeneratorOptions ) = pair ;
136+
137+ var tagHelperFeature = new StaticCompilationTagHelperFeature ( ) ;
138+ var discoveryProjectEngine = GetDiscoveryProjectEngine ( compilation . References . ToImmutableArray ( ) , tagHelperFeature ) ;
129139
130- return true ;
131- } , getHashCode : static a => a . Count ) ;
140+ tagHelperFeature . Compilation = compilation ;
141+ tagHelperFeature . TargetSymbol = compilation . Assembly ;
142+
143+ var result = tagHelperFeature . GetDescriptors ( ) ;
144+ RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromCompilationStop ( ) ;
145+ return result ;
146+ } ) ;
132147
133148 var tagHelpersFromReferences = compilation
134149 . Combine ( razorSourceGeneratorOptions )
@@ -171,7 +186,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
171186 var tagHelperFeature = new StaticCompilationTagHelperFeature ( ) ;
172187 var discoveryProjectEngine = GetDiscoveryProjectEngine ( compilation . References . ToImmutableArray ( ) , tagHelperFeature ) ;
173188
174- List < TagHelperDescriptor > descriptors = new ( ) ;
189+ using var pool = ArrayBuilderPool < TagHelperDescriptor > . GetPooledObject ( out var descriptors ) ;
175190 tagHelperFeature . Compilation = compilation ;
176191 foreach ( var reference in compilation . References )
177192 {
@@ -183,47 +198,84 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
183198 }
184199
185200 RazorSourceGeneratorEventSource . Log . DiscoverTagHelpersFromReferencesStop ( ) ;
186- return ( ICollection < TagHelperDescriptor > ) descriptors ;
201+ return descriptors . ToImmutable ( ) ;
187202 } ) ;
188203
189- var allTagHelpers = tagHelpersFromCompilation
204+ var allTagHelpers = tagHelpersFromComponents . Collect ( )
205+ . Combine ( tagHelpersFromCompilation )
190206 . Combine ( tagHelpersFromReferences )
191207 . Select ( static ( pair , _ ) =>
192208 {
193- var ( tagHelpersFromCompilation , tagHelpersFromReferences ) = pair ;
194- var count = tagHelpersFromCompilation . Count + tagHelpersFromReferences . Count ;
209+ var ( ( tagHelpersFromComponents , tagHelpersFromCompilation ) , tagHelpersFromReferences ) = pair ;
210+ var count = tagHelpersFromCompilation . Length + tagHelpersFromReferences . Length + tagHelpersFromComponents . Length ;
195211 if ( count == 0 )
196212 {
197- return Array . Empty < TagHelperDescriptor > ( ) ;
213+ return ImmutableArray < TagHelperDescriptor > . Empty ;
198214 }
199215
200- var allTagHelpers = new TagHelperDescriptor [ count ] ;
201- tagHelpersFromCompilation . CopyTo ( allTagHelpers , 0 ) ;
202- tagHelpersFromReferences . CopyTo ( allTagHelpers , tagHelpersFromCompilation . Count ) ;
216+ using var pool = ArrayBuilderPool < TagHelperDescriptor > . GetPooledObject ( out var allTagHelpers ) ;
217+ allTagHelpers . AddRange ( tagHelpersFromCompilation ) ;
218+ allTagHelpers . AddRange ( tagHelpersFromReferences ) ;
219+ allTagHelpers . AddRange ( tagHelpersFromComponents ) ;
203220
204- return allTagHelpers ;
221+ return allTagHelpers . ToImmutable ( ) ;
205222 } ) ;
206223
207224 var generatedOutput = sourceItems
208225 . Combine ( importFiles . Collect ( ) )
209- . Combine ( allTagHelpers )
226+ . WithLambdaComparer ( ( old , @new ) => old . Left . Equals ( @new . Left ) && old . Right . SequenceEqual ( @new . Right ) , ( a ) => a . GetHashCode ( ) )
210227 . Combine ( razorSourceGeneratorOptions )
211228 . Select ( static ( pair , _ ) =>
212229 {
213- var ( ( ( sourceItem , imports ) , allTagHelpers ) , razorSourceGeneratorOptions ) = pair ;
230+ var ( ( sourceItem , imports ) , razorSourceGeneratorOptions ) = pair ;
231+
232+ RazorSourceGeneratorEventSource . Log . ParseRazorDocumentStart ( sourceItem . RelativePhysicalPath ) ;
233+
234+ var projectEngine = GetGenerationProjectEngine ( sourceItem , imports , razorSourceGeneratorOptions ) ;
235+
236+ var document = projectEngine . ProcessInitialParse ( sourceItem ) ;
237+
238+ RazorSourceGeneratorEventSource . Log . ParseRazorDocumentStop ( sourceItem . RelativePhysicalPath ) ;
239+ return ( projectEngine , sourceItem . RelativePhysicalPath , document ) ;
240+ } )
241+
242+ // Add the tag helpers in, but ignore if they've changed or not, only reprocessing the actual document changed
243+ . Combine ( allTagHelpers )
244+ . WithLambdaComparer ( ( old , @new ) => old . Left . Equals ( @new . Left ) , ( item ) => item . GetHashCode ( ) )
245+ . Select ( ( pair , _ ) =>
246+ {
247+ var ( ( projectEngine , filePath , codeDocument ) , allTagHelpers ) = pair ;
248+ RazorSourceGeneratorEventSource . Log . RewriteTagHelpersStart ( filePath ) ;
214249
215- RazorSourceGeneratorEventSource . Log . RazorCodeGenerateStart ( sourceItem . FilePath ) ;
250+ codeDocument = projectEngine . ProcessTagHelpers ( codeDocument , allTagHelpers , checkForIdempotency : false ) ;
216251
217- // Add a generated suffix so tools, such as coverlet, consider the file to be generated
218- var hintName = GetIdentifierFromPath ( sourceItem . RelativePhysicalPath ) + ".g.cs" ;
252+ RazorSourceGeneratorEventSource . Log . RewriteTagHelpersStop ( filePath ) ;
253+ return ( projectEngine , filePath , codeDocument ) ;
254+ } )
255+
256+ // next we do a second parse, along with the helpers, but check for idempotency. If the tag helpers used on the previous parse match, the compiler can skip re-computing them
257+ . Combine ( allTagHelpers )
258+ . Select ( ( pair , _ ) =>
259+ {
219260
220- var projectEngine = GetGenerationProjectEngine ( allTagHelpers , sourceItem , imports , razorSourceGeneratorOptions ) ;
261+ var ( ( projectEngine , filePath , document ) , allTagHelpers ) = pair ;
262+ RazorSourceGeneratorEventSource . Log . CheckAndRewriteTagHelpersStart ( filePath ) ;
221263
222- var codeDocument = projectEngine . Process ( sourceItem ) ;
223- var csharpDocument = codeDocument . GetCSharpDocument ( ) ;
264+ document = projectEngine . ProcessTagHelpers ( document , allTagHelpers , checkForIdempotency : true ) ;
224265
225- RazorSourceGeneratorEventSource . Log . RazorCodeGenerateStop ( sourceItem . FilePath ) ;
226- return ( hintName , csharpDocument ) ;
266+ RazorSourceGeneratorEventSource . Log . CheckAndRewriteTagHelpersStop ( filePath ) ;
267+ return ( projectEngine , filePath , document ) ;
268+ } )
269+
270+ . Select ( ( pair , _ ) =>
271+ {
272+ var ( projectEngine , filePath , document ) = pair ;
273+ RazorSourceGeneratorEventSource . Log . RazorCodeGenerateStart ( filePath ) ;
274+ document = projectEngine . ProcessRemaining ( document ) ;
275+ var csharpDocument = document . CodeDocument . GetCSharpDocument ( ) ;
276+
277+ RazorSourceGeneratorEventSource . Log . RazorCodeGenerateStop ( filePath ) ;
278+ return ( filePath , csharpDocument ) ;
227279 } )
228280 . WithLambdaComparer ( static ( a , b ) =>
229281 {
@@ -238,7 +290,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
238290
239291 context . RegisterSourceOutput ( generatedOutput , static ( context , pair ) =>
240292 {
241- var ( hintName , csharpDocument ) = pair ;
293+ var ( filePath , csharpDocument ) = pair ;
294+
295+ // Add a generated suffix so tools, such as coverlet, consider the file to be generated
296+ var hintName = GetIdentifierFromPath ( filePath ) + ".g.cs" ;
297+
242298 RazorSourceGeneratorEventSource . Log . AddSyntaxTrees ( hintName ) ;
243299 for ( var i = 0 ; i < csharpDocument . Diagnostics . Count ; i ++ )
244300 {
0 commit comments