diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/SourceGeneratorProjectEngine.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/SourceGeneratorProjectEngine.cs index 67b0359dadf..1615b839e7c 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/SourceGeneratorProjectEngine.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/SourceGenerators/SourceGeneratorProjectEngine.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Razor.Language; using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Threading; -using Microsoft.AspNetCore.Razor.Language; namespace Microsoft.NET.Sdk.Razor.SourceGenerators; @@ -65,7 +63,11 @@ public SourceGeneratorRazorCodeDocument ProcessInitialParse(RazorProjectItem pro return new SourceGeneratorRazorCodeDocument(codeDocument); } - public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCodeDocument sgDocument, TagHelperCollection tagHelpers, bool checkForIdempotency, CancellationToken cancellationToken) + public SourceGeneratorRazorCodeDocument ProcessTagHelpers( + SourceGeneratorRazorCodeDocument sgDocument, + TagHelperCollection tagHelpers, + bool checkForIdempotency, + CancellationToken cancellationToken) { Debug.Assert(sgDocument.CodeDocument.GetPreTagHelperSyntaxTree() is not null); @@ -88,12 +90,11 @@ public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCo // re-run discovery to figure out which tag helpers are now in scope for this document codeDocument.SetTagHelpers(tagHelpers); _discoveryPhase.Execute(codeDocument, cancellationToken); - var tagHelpersInScope = codeDocument.GetRequiredTagHelperContext().TagHelpers; + + var newTagHelpersInScope = codeDocument.GetRequiredTagHelperContext().TagHelpers; // Check if any new tag helpers were added or ones we previously used were removed - var newVisibleTagHelpers = tagHelpersInScope.Except(previousTagHelpersInScope); - var newUnusedTagHelpers = previousUsedTagHelpers.Except(tagHelpersInScope); - if (!newVisibleTagHelpers.Any() && !newUnusedTagHelpers.Any()) + if (!RequiresRewrite(newTagHelpersInScope, previousTagHelpersInScope, previousUsedTagHelpers)) { // No newly visible tag helpers, and any that got removed weren't used by this document anyway return sgDocument; @@ -112,6 +113,56 @@ public SourceGeneratorRazorCodeDocument ProcessTagHelpers(SourceGeneratorRazorCo return new SourceGeneratorRazorCodeDocument(codeDocument); } + private static bool RequiresRewrite( + TagHelperCollection newTagHelpers, + TagHelperCollection previousTagHelpers, + TagHelperCollection previousUsedTagHelpers) + { + // Check if any new tag helpers were added (that weren't in scope before) + // Check if any previously used tag helpers were removed (no longer in scope) + return HasAnyNotIn(newTagHelpers, previousTagHelpers) || + HasAnyNotIn(previousUsedTagHelpers, newTagHelpers); + } + + /// + /// Determines whether the first collection contains any tag helper descriptors that are not present + /// in the second collection. + /// + /// The collection to check for unique items. + /// The collection to compare against. + /// + /// if contains any descriptors not present in + /// ; otherwise, . + /// + private static bool HasAnyNotIn(TagHelperCollection first, TagHelperCollection second) + { + if (first.IsEmpty) + { + return false; + } + + if (second.IsEmpty) + { + return true; + } + + if (first.Equals(second)) + { + return false; + } + + // For each item in the first collection, check if it exists in the second collection + foreach (var item in first) + { + if (!second.Contains(item)) + { + return true; + } + } + + return false; + } + public SourceGeneratorRazorCodeDocument ProcessRemaining(SourceGeneratorRazorCodeDocument sgDocument, CancellationToken cancellationToken) { var codeDocument = sgDocument.CodeDocument;