Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Razor;
Expand All @@ -21,6 +23,8 @@ public partial class RazorSourceGenerator : IIncrementalGenerator
{
private static RazorSourceGeneratorEventSource Log => RazorSourceGeneratorEventSource.Log;

private ReadOnlyDictionary<MetadataReference, ImmutableArray<TagHelperDescriptor>> _descriptorCache = new(new Dictionary<MetadataReference, ImmutableArray<TagHelperDescriptor>>());

// Testing usage only.
private readonly string? _testSuppressUniqueIds;

Expand Down Expand Up @@ -195,7 +199,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

return hasRazorFilesA == hasRazorFilesB;
})
.Select(static (pair, _) =>
.Select((pair, _) =>
{
var ((compilation, razorSourceGeneratorOptions), hasRazorFiles) = pair;
if (!hasRazorFiles)
Expand All @@ -211,14 +215,34 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// So, we start with a larger capacity to avoid extra array copies.
var results = new List<TagHelperDescriptor>(capacity: 128);

var oldCachedDescriptors = _descriptorCache;
var newCachedDescriptors = new Dictionary<MetadataReference, ImmutableArray<TagHelperDescriptor>>(oldCachedDescriptors.Count);
var descriptorsBuilder = new List<TagHelperDescriptor>();

foreach (var reference in compilation.References)
{
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
if (!oldCachedDescriptors.TryGetValue(reference, out var cachedDescriptors))
{
tagHelperFeature.CollectDescriptors(assembly, results);
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
{
tagHelperFeature.CollectDescriptors(assembly, descriptorsBuilder);
Copy link
Member

@DustinCampbell DustinCampbell Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This calls into the ITagHelperDescriptorProvider implementations, which already have caches of TagHelperProvider[] per IAssemblySymbol. The problem that you mentioned offline is around Compilation.GetTypeByMetaname which I believe the ITagHelperDescriptorProviders call before they hit their caches. It seems do-able to push that data into the IAssemblySymbol cache rather than create another.

}

cachedDescriptors = descriptorsBuilder.Count > 0 ? descriptorsBuilder.ToImmutableArray() : [];
descriptorsBuilder.Clear();
}

newCachedDescriptors[reference] = cachedDescriptors;

// Avoid List.AddRange to avoid boxing of the IA and enumerator allocation
foreach (var descriptor in cachedDescriptors)
{
results.Add(descriptor);
}
}

_descriptorCache = new ReadOnlyDictionary<MetadataReference, ImmutableArray<TagHelperDescriptor>>(newCachedDescriptors);

RazorSourceGeneratorEventSource.Log.DiscoverTagHelpersFromReferencesStop();

return results;
Expand Down