Skip to content

Commit d380c2d

Browse files
Optimize tag helper change detection logic in source generator
Refactors the tag helper change detection logic to be more efficient and avoid allocations by directly comparing collections rather than using LINQ's Except method.
1 parent 8503ca5 commit d380c2d

File tree

1 file changed

+59
-8
lines changed

1 file changed

+59
-8
lines changed

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/SourceGeneratorProjectEngine.cs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Razor.Language;
45
using System;
5-
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Linq;
87
using System.Threading;
9-
using Microsoft.AspNetCore.Razor.Language;
108

119
namespace Microsoft.NET.Sdk.Razor.SourceGenerators;
1210

@@ -65,7 +63,11 @@ public SourceGeneratorRazorCodeDocument ProcessInitialParse(RazorProjectItem pro
6563
return new SourceGeneratorRazorCodeDocument(codeDocument);
6664
}
6765

68-
public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCodeDocument sgDocument, TagHelperCollection tagHelpers, bool checkForIdempotency, CancellationToken cancellationToken)
66+
public SourceGeneratorRazorCodeDocument ProcessTagHelpers(
67+
SourceGeneratorRazorCodeDocument sgDocument,
68+
TagHelperCollection tagHelpers,
69+
bool checkForIdempotency,
70+
CancellationToken cancellationToken)
6971
{
7072
Debug.Assert(sgDocument.CodeDocument.GetPreTagHelperSyntaxTree() is not null);
7173

@@ -88,12 +90,11 @@ public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCo
8890
// re-run discovery to figure out which tag helpers are now in scope for this document
8991
codeDocument.SetTagHelpers(tagHelpers);
9092
_discoveryPhase.Execute(codeDocument, cancellationToken);
91-
var tagHelpersInScope = codeDocument.GetRequiredTagHelperContext().TagHelpers;
93+
94+
var newTagHelpersInScope = codeDocument.GetRequiredTagHelperContext().TagHelpers;
9295

9396
// Check if any new tag helpers were added or ones we previously used were removed
94-
var newVisibleTagHelpers = tagHelpersInScope.Except(previousTagHelpersInScope);
95-
var newUnusedTagHelpers = previousUsedTagHelpers.Except(tagHelpersInScope);
96-
if (!newVisibleTagHelpers.Any() && !newUnusedTagHelpers.Any())
97+
if (!RequiresRewrite(newTagHelpersInScope, previousTagHelpersInScope, previousUsedTagHelpers))
9798
{
9899
// No newly visible tag helpers, and any that got removed weren't used by this document anyway
99100
return sgDocument;
@@ -112,6 +113,56 @@ public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCo
112113
return new SourceGeneratorRazorCodeDocument(codeDocument);
113114
}
114115

116+
private static bool RequiresRewrite(
117+
TagHelperCollection newTagHelpers,
118+
TagHelperCollection previousTagHelpers,
119+
TagHelperCollection previousUsedTagHelpers)
120+
{
121+
// Check if any new tag helpers were added (that weren't in scope before)
122+
// Check if any previously used tag helpers were removed (no longer in scope)
123+
return HasAnyNotIn(newTagHelpers, previousTagHelpers) ||
124+
HasAnyNotIn(previousUsedTagHelpers, newTagHelpers);
125+
}
126+
127+
/// <summary>
128+
/// Determines whether the first collection contains any tag helper descriptors that are not present
129+
/// in the second collection.
130+
/// </summary>
131+
/// <param name="first">The collection to check for unique items.</param>
132+
/// <param name="second">The collection to compare against.</param>
133+
/// <returns>
134+
/// <see langword="true"/> if <paramref name="first"/> contains any descriptors not present in
135+
/// <paramref name="second"/>; otherwise, <see langword="false"/>.
136+
/// </returns>
137+
private static bool HasAnyNotIn(TagHelperCollection first, TagHelperCollection second)
138+
{
139+
if (first.IsEmpty)
140+
{
141+
return false;
142+
}
143+
144+
if (second.IsEmpty)
145+
{
146+
return true;
147+
}
148+
149+
if (first.Equals(second))
150+
{
151+
return false;
152+
}
153+
154+
// For each item in the first collection, check if it exists in the second collection
155+
foreach (var item in first)
156+
{
157+
if (!second.Contains(item))
158+
{
159+
return true;
160+
}
161+
}
162+
163+
return false;
164+
}
165+
115166
public SourceGeneratorRazorCodeDocument ProcessRemaining(SourceGeneratorRazorCodeDocument sgDocument, CancellationToken cancellationToken)
116167
{
117168
var codeDocument = sgDocument.CodeDocument;

0 commit comments

Comments
 (0)