Skip to content

Commit 0890654

Browse files
authored
Add an option to tag helper providers to allow filtering (#29688)
As part of converting to source generators, one of the features we miss from MSBuild is the ability to no-op and cache results operations if inputs are unchanged (target incrementalism). In this particular case, we're trying to caching tag helper descriptors instead of discovering it every build. One fairly simple solution is to cache descriptors for reference assemblies (that typically remain unchanged between compilations), and the current assembly separately. The former can be cached and invalidated when metadatareferences change while the latter has a smaller set of types to search.
1 parent 5d42ed0 commit 0890654

10 files changed

+396
-43
lines changed

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/src/ViewComponentTagHelperDescriptorProvider.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -37,17 +37,23 @@ public void Execute(TagHelperDescriptorProviderContext context)
3737

3838
var types = new List<INamedTypeSymbol>();
3939
var visitor = new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, types);
40+
var discoveryMode = context.Items.GetTagHelperDiscoveryFilter();
4041

41-
// We always visit the global namespace.
42-
visitor.Visit(compilation.Assembly.GlobalNamespace);
42+
if ((discoveryMode & TagHelperDiscoveryFilter.CurrentCompilation) == TagHelperDiscoveryFilter.CurrentCompilation)
43+
{
44+
visitor.Visit(compilation.Assembly.GlobalNamespace);
45+
}
4346

44-
foreach (var reference in compilation.References)
47+
if ((discoveryMode & TagHelperDiscoveryFilter.ReferenceAssemblies) == TagHelperDiscoveryFilter.ReferenceAssemblies)
4548
{
46-
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
49+
foreach (var reference in compilation.References)
4750
{
48-
if (IsTagHelperAssembly(assembly))
51+
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
4952
{
50-
visitor.Visit(assembly.GlobalNamespace);
53+
if (IsTagHelperAssembly(assembly))
54+
{
55+
visitor.Visit(assembly.GlobalNamespace);
56+
}
5157
}
5258
}
5359
}

src/Razor/Microsoft.AspNetCore.Mvc.Razor.Extensions/test/ViewComponentTagHelperDescriptorProviderTest.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using Microsoft.AspNetCore.Razor.Language;
@@ -65,5 +65,63 @@ public class StringParameterViewComponent
6565
// Assert
6666
Assert.Single(context.Results, d => TagHelperDescriptorComparer.Default.Equals(d, expectedDescriptor));
6767
}
68+
69+
[Fact]
70+
public void DescriptorProvider_WithCurrentCompilationFilter_FindsDescriptorFromCurrentCompilation()
71+
{
72+
// Arrange
73+
var code = @"
74+
public class StringParameterViewComponent
75+
{
76+
public string Invoke(string foo, string bar) => null;
77+
}
78+
";
79+
80+
var compilation = MvcShim.BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
81+
82+
var context = TagHelperDescriptorProviderContext.Create();
83+
context.SetCompilation(compilation);
84+
context.Items.SetTagHelperDiscoveryFilter(TagHelperDiscoveryFilter.CurrentCompilation);
85+
86+
var provider = new ViewComponentTagHelperDescriptorProvider()
87+
{
88+
Engine = RazorProjectEngine.CreateEmpty().Engine,
89+
};
90+
91+
// Act
92+
provider.Execute(context);
93+
94+
// Assert
95+
Assert.Single(context.Results);
96+
}
97+
98+
[Fact]
99+
public void DescriptorProvider_WithReferenceAssembliesFilter_DoesNotFindDescriptorFromCurrentCompilation()
100+
{
101+
// Arrange
102+
var code = @"
103+
public class StringParameterViewComponent
104+
{
105+
public string Invoke(string foo, string bar) => null;
106+
}
107+
";
108+
109+
var compilation = MvcShim.BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code));
110+
111+
var context = TagHelperDescriptorProviderContext.Create();
112+
context.SetCompilation(compilation);
113+
context.Items.SetTagHelperDiscoveryFilter(TagHelperDiscoveryFilter.ReferenceAssemblies);
114+
115+
var provider = new ViewComponentTagHelperDescriptorProvider()
116+
{
117+
Engine = RazorProjectEngine.CreateEmpty().Engine,
118+
};
119+
120+
// Act
121+
provider.Execute(context);
122+
123+
// Assert
124+
Assert.Empty(context.Results);
125+
}
68126
}
69127
}

src/Razor/Microsoft.CodeAnalysis.Razor/src/ComponentTagHelperDescriptorProvider.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,24 @@ public void Execute(TagHelperDescriptorProviderContext context)
4040
var types = new List<INamedTypeSymbol>();
4141
var visitor = new ComponentTypeVisitor(symbols, types);
4242

43-
// Visit the primary output of this compilation, as well as all references.
44-
visitor.Visit(compilation.Assembly);
45-
foreach (var reference in compilation.References)
43+
var discoveryMode = context.Items.GetTagHelperDiscoveryFilter();
44+
45+
if ((discoveryMode & TagHelperDiscoveryFilter.CurrentCompilation) == TagHelperDiscoveryFilter.CurrentCompilation)
46+
{
47+
// Visit the primary output of this compilation
48+
visitor.Visit(compilation.Assembly);
49+
}
50+
51+
if ((discoveryMode & TagHelperDiscoveryFilter.ReferenceAssemblies) == TagHelperDiscoveryFilter.ReferenceAssemblies)
4652
{
47-
// We ignore .netmodules here - there really isn't a case where they are used by user code
48-
// even though the Roslyn APIs all support them.
49-
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
53+
foreach (var reference in compilation.References)
5054
{
51-
visitor.Visit(assembly);
55+
// We ignore .netmodules here - there really isn't a case where they are used by user code
56+
// even though the Roslyn APIs all support them.
57+
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
58+
{
59+
visitor.Visit(assembly);
60+
}
5261
}
5362
}
5463

src/Razor/Microsoft.CodeAnalysis.Razor/src/DefaultTagHelperDescriptorProvider.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -34,17 +34,23 @@ public void Execute(TagHelperDescriptorProviderContext context)
3434

3535
var types = new List<INamedTypeSymbol>();
3636
var visitor = new TagHelperTypeVisitor(iTagHelper, types);
37+
var discoveryMode = context.Items.GetTagHelperDiscoveryFilter();
3738

38-
// We always visit the global namespace.
39-
visitor.Visit(compilation.Assembly.GlobalNamespace);
39+
if ((discoveryMode & TagHelperDiscoveryFilter.CurrentCompilation) == TagHelperDiscoveryFilter.CurrentCompilation)
40+
{
41+
visitor.Visit(compilation.Assembly.GlobalNamespace);
42+
}
4043

41-
foreach (var reference in compilation.References)
44+
if ((discoveryMode & TagHelperDiscoveryFilter.ReferenceAssemblies) == TagHelperDiscoveryFilter.ReferenceAssemblies)
4245
{
43-
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
46+
foreach (var reference in compilation.References)
4447
{
45-
if (IsTagHelperAssembly(assembly))
48+
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
4649
{
47-
visitor.Visit(assembly.GlobalNamespace);
50+
if (IsTagHelperAssembly(assembly))
51+
{
52+
visitor.Visit(assembly.GlobalNamespace);
53+
}
4854
}
4955
}
5056
}

src/Razor/Microsoft.CodeAnalysis.Razor/src/EventHandlerTagHelperDescriptorProvider.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,43 +28,41 @@ public void Execute(TagHelperDescriptorProviderContext context)
2828
return;
2929
}
3030

31-
var bindMethods = compilation.GetTypeByMetadataName(ComponentsApi.IComponent.FullTypeName);
32-
if (bindMethods == null)
31+
if (compilation.GetTypeByMetadataName(ComponentsApi.EventHandlerAttribute.FullTypeName) is not INamedTypeSymbol eventHandlerAttribute)
3332
{
34-
// If we can't find IComponent, then just bail. We won't be able to compile the
35-
// generated code anyway.
33+
// If we can't find EventHandlerAttribute, then just bail. We won't discover anything.
3634
return;
3735
}
3836

39-
var eventHandlerData = GetEventHandlerData(compilation);
37+
var eventHandlerData = GetEventHandlerData(context, compilation, eventHandlerAttribute);
4038

4139
foreach (var tagHelper in CreateEventHandlerTagHelpers(eventHandlerData))
4240
{
4341
context.Results.Add(tagHelper);
4442
}
4543
}
4644

47-
private List<EventHandlerData> GetEventHandlerData(Compilation compilation)
45+
private List<EventHandlerData> GetEventHandlerData(TagHelperDescriptorProviderContext context, Compilation compilation, INamedTypeSymbol eventHandlerAttribute)
4846
{
49-
var eventHandlerAttribute = compilation.GetTypeByMetadataName(ComponentsApi.EventHandlerAttribute.FullTypeName);
50-
if (eventHandlerAttribute == null)
51-
{
52-
// This won't likely happen, but just in case.
53-
return new List<EventHandlerData>();
54-
}
55-
5647
var types = new List<INamedTypeSymbol>();
5748
var visitor = new EventHandlerDataVisitor(types);
5849

59-
// Visit the primary output of this compilation, as well as all references.
60-
visitor.Visit(compilation.Assembly);
61-
foreach (var reference in compilation.References)
50+
var discoveryMode = context.Items.GetTagHelperDiscoveryFilter();
51+
if ((discoveryMode & TagHelperDiscoveryFilter.CurrentCompilation) == TagHelperDiscoveryFilter.CurrentCompilation)
52+
{
53+
visitor.Visit(compilation.Assembly);
54+
}
55+
56+
if ((discoveryMode & TagHelperDiscoveryFilter.ReferenceAssemblies) == TagHelperDiscoveryFilter.ReferenceAssemblies)
6257
{
63-
// We ignore .netmodules here - there really isn't a case where they are used by user code
64-
// even though the Roslyn APIs all support them.
65-
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
58+
foreach (var reference in compilation.References)
6659
{
67-
visitor.Visit(assembly);
60+
// We ignore .netmodules here - there really isn't a case where they are used by user code
61+
// even though the Roslyn APIs all support them.
62+
if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly)
63+
{
64+
visitor.Visit(assembly);
65+
}
6866
}
6967
}
7068

src/Razor/Microsoft.CodeAnalysis.Razor/src/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.OmniSharpPlugin.StrongNamed, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
66
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
77
[assembly: InternalsVisibleTo("rzc, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
8+
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
89
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
910
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Test.Common, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
1011
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.LanguageServer.Common, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#nullable enable
5+
6+
using System;
7+
8+
namespace Microsoft.CodeAnalysis.Razor
9+
{
10+
[Flags]
11+
internal enum TagHelperDiscoveryFilter
12+
{
13+
CurrentCompilation = 1,
14+
ReferenceAssemblies = 2,
15+
Default = CurrentCompilation | ReferenceAssemblies,
16+
};
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
#nullable enable
5+
6+
using Microsoft.AspNetCore.Razor.Language;
7+
8+
namespace Microsoft.CodeAnalysis.Razor
9+
{
10+
internal static class TagHelperDiscoveryFilterExtensions
11+
{
12+
private static readonly object TagHelperDiscoveryModeKey = new object();
13+
14+
public static TagHelperDiscoveryFilter GetTagHelperDiscoveryFilter(this ItemCollection items)
15+
{
16+
if (items.Count == 0 || items[TagHelperDiscoveryModeKey] is not TagHelperDiscoveryFilter filter)
17+
{
18+
return TagHelperDiscoveryFilter.Default;
19+
}
20+
21+
return filter;
22+
}
23+
24+
public static void SetTagHelperDiscoveryFilter(this ItemCollection items, TagHelperDiscoveryFilter filter)
25+
{
26+
items[TagHelperDiscoveryModeKey] = filter;
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)