@@ -58,18 +58,18 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, Cancellation
5858 codeDocument . SetPreTagHelperSyntaxTree ( syntaxTree ) ;
5959 }
6060
61- private static ReadOnlySpan < char > GetSpanWithoutGlobalPrefix ( string s )
61+ internal static ReadOnlyMemory < char > GetMemoryWithoutGlobalPrefix ( string s )
6262 {
6363 const string globalPrefix = "global::" ;
6464
65- var span = s . AsSpan ( ) ;
65+ var mem = s . AsMemory ( ) ;
6666
67- if ( span . StartsWith ( globalPrefix . AsSpan ( ) , StringComparison . Ordinal ) )
67+ if ( mem . Span . StartsWith ( globalPrefix . AsSpan ( ) , StringComparison . Ordinal ) )
6868 {
69- return span [ globalPrefix . Length ..] ;
69+ return mem [ globalPrefix . Length ..] ;
7070 }
7171
72- return span ;
72+ return mem ;
7373 }
7474
7575 internal abstract class DirectiveVisitor : SyntaxWalker
@@ -241,7 +241,7 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
241241 continue ;
242242 }
243243
244- switch ( GetSpanWithoutGlobalPrefix ( addTagHelper . TypePattern ) )
244+ switch ( GetMemoryWithoutGlobalPrefix ( addTagHelper . TypePattern ) . Span )
245245 {
246246 case [ '*' ] :
247247 AddMatches ( nonComponentTagHelpers ) ;
@@ -287,7 +287,7 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
287287 continue ;
288288 }
289289
290- switch ( GetSpanWithoutGlobalPrefix ( removeTagHelper . TypePattern ) )
290+ switch ( GetMemoryWithoutGlobalPrefix ( removeTagHelper . TypePattern ) . Span )
291291 {
292292 case [ '*' ] :
293293 RemoveMatches ( nonComponentTagHelpers ) ;
@@ -334,7 +334,9 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
334334
335335 internal sealed class ComponentDirectiveVisitor : DirectiveVisitor
336336 {
337- private readonly List < TagHelperDescriptor > _nonFullyQualifiedComponents = [ ] ;
337+ // The list values in this dictionary are pooled
338+ private readonly Dictionary < ReadOnlyMemory < char > , List < TagHelperDescriptor > > _typeNamespaceToNonFullyQualifiedComponents = new Dictionary < ReadOnlyMemory < char > , List < TagHelperDescriptor > > ( ReadOnlyMemoryOfCharComparer . Instance ) ;
339+ private List < TagHelperDescriptor > ? _nonFullyQualifiedComponentsWithEmptyTypeNamespace ;
338340
339341 private string ? _filePath ;
340342 private RazorSourceDocument ? _source ;
@@ -347,6 +349,7 @@ public void Initialize(string filePath, IReadOnlyList<TagHelperDescriptor> tagHe
347349 Debug . Assert ( ! IsInitialized ) ;
348350
349351 _filePath = filePath ;
352+ _nonFullyQualifiedComponentsWithEmptyTypeNamespace = ListPool < TagHelperDescriptor > . Default . Get ( ) ;
350353
351354 foreach ( var tagHelper in tagHelpers . AsEnumerable ( ) )
352355 {
@@ -363,25 +366,31 @@ public void Initialize(string filePath, IReadOnlyList<TagHelperDescriptor> tagHe
363366 continue ;
364367 }
365368
366- _nonFullyQualifiedComponents . Add ( tagHelper ) ;
369+ var tagHelperTypeNamespace = tagHelper . GetTypeNamespace ( ) . AsMemory ( ) ;
367370
368- if ( currentNamespace is null )
371+ if ( tagHelperTypeNamespace . IsEmpty )
369372 {
370- continue ;
373+ _nonFullyQualifiedComponentsWithEmptyTypeNamespace . Add ( tagHelper ) ;
371374 }
372-
373- if ( tagHelper . IsChildContentTagHelper )
375+ else
374376 {
375- // If this is a child content tag helper, we want to add it if it's original type is in scope.
376- // E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in scope.
377- if ( IsTypeInScope ( tagHelper , currentNamespace ) )
377+ if ( ! _typeNamespaceToNonFullyQualifiedComponents . TryGetValue ( tagHelperTypeNamespace , out var tagHelpersList ) )
378378 {
379- AddMatch ( tagHelper ) ;
379+ tagHelpersList = ListPool < TagHelperDescriptor > . Default . Get ( ) ;
380+ _typeNamespaceToNonFullyQualifiedComponents . Add ( tagHelperTypeNamespace , tagHelpersList ) ;
380381 }
382+
383+ tagHelpersList . Add ( tagHelper ) ;
381384 }
382- else if ( IsTypeInScope ( tagHelper , currentNamespace ) )
385+
386+ if ( currentNamespace is null )
383387 {
384- // Also, if the type is already in scope of the document's namespace, using isn't necessary.
388+ continue ;
389+ }
390+
391+ if ( IsTypeNamespaceInScope ( tagHelperTypeNamespace . Span , currentNamespace ) )
392+ {
393+ // If the type is already in scope of the document's namespace, using isn't necessary.
385394 AddMatch ( tagHelper ) ;
386395 }
387396 }
@@ -391,7 +400,18 @@ public void Initialize(string filePath, IReadOnlyList<TagHelperDescriptor> tagHe
391400
392401 public override void Reset ( )
393402 {
394- _nonFullyQualifiedComponents . Clear ( ) ;
403+ if ( _nonFullyQualifiedComponentsWithEmptyTypeNamespace != null )
404+ {
405+ ListPool < TagHelperDescriptor > . Default . Return ( _nonFullyQualifiedComponentsWithEmptyTypeNamespace ) ;
406+ _nonFullyQualifiedComponentsWithEmptyTypeNamespace = null ;
407+ }
408+
409+ foreach ( var ( _, tagHelpers ) in _typeNamespaceToNonFullyQualifiedComponents )
410+ {
411+ ListPool < TagHelperDescriptor > . Default . Return ( tagHelpers ) ;
412+ }
413+
414+ _typeNamespaceToNonFullyQualifiedComponents . Clear ( ) ;
395415 _filePath = null ;
396416 _source = null ;
397417
@@ -407,6 +427,8 @@ public override void Visit(RazorSyntaxTree tree)
407427
408428 public override void VisitRazorDirective ( RazorDirectiveSyntax node )
409429 {
430+ var componentsWithEmptyTypeNamespace = _nonFullyQualifiedComponentsWithEmptyTypeNamespace . AssumeNotNull ( ) ;
431+
410432 var descendantLiterals = node . DescendantNodes ( ) ;
411433 foreach ( var child in descendantLiterals )
412434 {
@@ -455,22 +477,30 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
455477 continue ;
456478 }
457479
458- foreach ( var tagHelper in _nonFullyQualifiedComponents )
480+ if ( _typeNamespaceToNonFullyQualifiedComponents . Count == 0 && componentsWithEmptyTypeNamespace . Count == 0 )
481+ {
482+ // There aren't any non qualified components to add
483+ continue ;
484+ }
485+
486+ // Add all tag helpers that have an empty type namespace
487+ foreach ( var tagHelper in componentsWithEmptyTypeNamespace )
459488 {
460489 Debug . Assert ( ! tagHelper . IsComponentFullyQualifiedNameMatch , "We've already processed these." ) ;
461490
462- if ( tagHelper . IsChildContentTagHelper )
463- {
464- // If this is a child content tag helper, we want to add it if it's original type is in scope of the given namespace.
465- // E.g, if the type name is `Test.MyComponent.ChildContent`, we want to add it if `Test.MyComponent` is in this namespace.
466- if ( IsTypeInNamespace ( tagHelper , @namespace ) )
467- {
468- AddMatch ( tagHelper ) ;
469- }
470- }
471- else if ( IsTypeInNamespace ( tagHelper , @namespace ) )
491+ AddMatch ( tagHelper ) ;
492+ }
493+
494+ // Remove global:: prefix from namespace.
495+ var normalizedNamespace = GetMemoryWithoutGlobalPrefix ( @namespace ) ;
496+
497+ // Add all tag helpers with a matching namespace
498+ if ( _typeNamespaceToNonFullyQualifiedComponents . TryGetValue ( normalizedNamespace , out var tagHelpers ) )
499+ {
500+ foreach ( var tagHelper in tagHelpers )
472501 {
473- // If the type is at the top-level or if the type's namespace matches the using's namespace, add it.
502+ Debug . Assert ( ! tagHelper . IsComponentFullyQualifiedNameMatch , "We've already processed these." ) ;
503+
474504 AddMatch ( tagHelper ) ;
475505 }
476506 }
@@ -480,32 +510,14 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node)
480510 }
481511 }
482512
483- internal static bool IsTypeInNamespace ( TagHelperDescriptor tagHelper , string @namespace )
484- {
485- var typeNamespace = tagHelper . GetTypeNamespace ( ) ;
486-
487- if ( typeNamespace . IsNullOrEmpty ( ) )
488- {
489- // Either the typeName is not the full type name or this type is at the top level.
490- return true ;
491- }
492-
493- // Remove global:: prefix from namespace.
494- var normalizedNamespaceSpan = GetSpanWithoutGlobalPrefix ( @namespace ) ;
495-
496- return normalizedNamespaceSpan . Equals ( typeNamespace . AsSpan ( ) , StringComparison . Ordinal ) ;
497- }
498-
499- // Check if the given type is already in scope given the namespace of the current document.
513+ // Check if a type's namespace is already in scope given the namespace of the current document.
500514 // E.g,
501515 // If the namespace of the document is `MyComponents.Components.Shared`,
502516 // then the types `MyComponents.FooComponent`, `MyComponents.Components.BarComponent`, `MyComponents.Components.Shared.BazComponent` are all in scope.
503517 // Whereas `MyComponents.SomethingElse.OtherComponent` is not in scope.
504- internal static bool IsTypeInScope ( TagHelperDescriptor tagHelper , string @namespace )
518+ internal static bool IsTypeNamespaceInScope ( ReadOnlySpan < char > typeNamespace , string @namespace )
505519 {
506- var typeNamespace = tagHelper . GetTypeNamespace ( ) ;
507-
508- if ( typeNamespace . IsNullOrEmpty ( ) )
520+ if ( typeNamespace . IsEmpty )
509521 {
510522 // Either the typeName is not the full type name or this type is at the top level.
511523 return true ;
0 commit comments