Skip to content

Commit 261d2e1

Browse files
TagHelperCollection Part 3: Optimize tag helper change detection logic in source generator (#12506)
| [Prelude](#12503) | [Part 1](#12504) | [Part 2](#12505) | Part 3 | [Part 4](#12507) | [Part 5](#12509) | This is a relatively small change to remove LINQ from the Razor source generator and use `TagHelperCollection` when checking for added/removed tag helpers. ---- CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2842169&view=results Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2842240&view=results
2 parents 2654bc4 + 587518c commit 261d2e1

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)