diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.IsViewComponentResult.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.IsViewComponentResult.cs new file mode 100644 index 00000000000..9bf2b4c21a0 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.IsViewComponentResult.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; + +namespace Microsoft.CodeAnalysis.Razor.Compiler.Language.Extensions; + +internal static partial class INamedTypeSymbolExtensions +{ + private sealed partial class Cache + { + private sealed class IsViewComponentResult + { + public bool IsViewComponent { get; } + public INamedTypeSymbol ViewComponentAttribute { get; } + public INamedTypeSymbol? NonViewComponentAttribute { get; } + + public IsViewComponentResult(INamedTypeSymbol symbol, INamedTypeSymbol viewComponentAttribute, INamedTypeSymbol? nonViewComponentAttribute) + { + ViewComponentAttribute = viewComponentAttribute; + NonViewComponentAttribute = nonViewComponentAttribute; + + if (symbol.DeclaredAccessibility != Accessibility.Public || + symbol.IsAbstract || + symbol.IsGenericType || + AttributeIsDefined(symbol, nonViewComponentAttribute)) + { + IsViewComponent = false; + } + else + { + IsViewComponent = symbol.Name.EndsWith(ViewComponentTypes.ViewComponentSuffix, StringComparison.Ordinal) || + AttributeIsDefined(symbol, viewComponentAttribute); + } + } + + public bool IsMatchingCache(INamedTypeSymbol viewComponentAttribute, INamedTypeSymbol? nonViewComponentAttribute) + { + return SymbolEqualityComparer.Default.Equals(ViewComponentAttribute, viewComponentAttribute) + && SymbolEqualityComparer.Default.Equals(NonViewComponentAttribute, nonViewComponentAttribute); + } + + private static bool AttributeIsDefined(INamedTypeSymbol type, INamedTypeSymbol? queryAttribute) + { + if (queryAttribute == null) + { + return false; + } + + var currentType = type; + while (currentType != null) + { + foreach (var attribute in currentType.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, queryAttribute)) + { + return true; + } + } + + currentType = currentType.BaseType; + } + + return false; + } + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.cs new file mode 100644 index 00000000000..b8c4914f065 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.Cache.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.CodeAnalysis.Razor.Compiler.Language.Extensions; + +internal static partial class INamedTypeSymbolExtensions +{ + private sealed partial class Cache(INamedTypeSymbol symbol) + { + private readonly INamedTypeSymbol _symbol = symbol; + private IsViewComponentResult? _isViewComponentResult; + + public bool IsViewComponent(INamedTypeSymbol viewComponentAttribute, INamedTypeSymbol? nonViewComponentAttribute) + { + var isViewComponentResult = _isViewComponentResult; + if (isViewComponentResult is null + || !isViewComponentResult.IsMatchingCache(viewComponentAttribute, nonViewComponentAttribute)) + { + isViewComponentResult = new IsViewComponentResult(_symbol, viewComponentAttribute, nonViewComponentAttribute); + _isViewComponentResult = isViewComponentResult; + } + + return isViewComponentResult.IsViewComponent; + } + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.cs new file mode 100644 index 00000000000..1f20c3f3817 --- /dev/null +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Extensions/INamedTypeSymbolExtensions.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +namespace Microsoft.CodeAnalysis.Razor.Compiler.Language.Extensions; + +internal static partial class INamedTypeSymbolExtensions +{ + private static readonly ConditionalWeakTable s_instance = new(); + + public static bool IsViewComponent(this INamedTypeSymbol symbol, INamedTypeSymbol viewComponentAttribute, INamedTypeSymbol? nonViewComponentAttribute) + { + var cache = s_instance.GetValue(symbol, static s => new Cache(s)); + + return cache.IsViewComponent(viewComponentAttribute, nonViewComponentAttribute); + } +} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypeVisitor.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypeVisitor.cs index 96f3982c579..9886bfe47ab 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypeVisitor.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Mvc/ViewComponentTypeVisitor.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor.Compiler.Language.Extensions; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions; @@ -61,33 +63,6 @@ internal bool IsViewComponent(INamedTypeSymbol symbol) return false; } - if (symbol.DeclaredAccessibility != Accessibility.Public || - symbol.IsAbstract || - symbol.IsGenericType || - AttributeIsDefined(symbol, _nonViewComponentAttribute)) - { - return false; - } - - return symbol.Name.EndsWith(ViewComponentTypes.ViewComponentSuffix, StringComparison.Ordinal) || - AttributeIsDefined(symbol, _viewComponentAttribute); - } - - private static bool AttributeIsDefined(INamedTypeSymbol? type, INamedTypeSymbol? queryAttribute) - { - if (type == null || queryAttribute == null) - { - return false; - } - - foreach (var attribute in type.GetAttributes()) - { - if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, queryAttribute)) - { - return true; - } - } - - return AttributeIsDefined(type.BaseType, queryAttribute); + return symbol.IsViewComponent(_viewComponentAttribute, _nonViewComponentAttribute); } }