@@ -17,26 +17,25 @@ internal static partial class RoslynHelpers
17
17
/// <summary>
18
18
/// Replacement for <see cref="SyntaxValueProvider.ForAttributeWithMetadataName" /> that handles generic attributes correctly.
19
19
/// </summary>
20
- public static IncrementalValuesProvider < TypeWithAttributeDeclarationContext > ForTypesWithAttributeDeclaration (
21
- this SyntaxValueProvider provider , string attributeFullyQualifiedName ,
20
+ public static IncrementalValuesProvider < TypeWithAttributeDeclarationContext > ForTypesWithAttributeDeclarations (
21
+ this SyntaxValueProvider provider ,
22
+ string [ ] attributeFullyQualifiedNames ,
22
23
Func < BaseTypeDeclarationSyntax , CancellationToken , bool > predicate )
23
24
{
24
- NameSyntax attributeNameSyntax = SyntaxFactory . ParseName ( attributeFullyQualifiedName ) ;
25
- string attributeName = GetTypeName ( attributeNameSyntax , out int attributeArity ) ;
26
- ImmutableArray < string > attributeNamespace = GetNamespaceTokens ( attributeNameSyntax ) ;
27
- string ? attributeNameMinusSuffix = attributeName . EndsWith ( "Attribute" , StringComparison . Ordinal ) ? attributeName [ ..^ "Attribute" . Length ] : null ;
25
+ Debug . Assert ( attributeFullyQualifiedNames . Length is > 0 and <= 3 , "Does not optimize for large lists of attributes." ) ;
26
+ ParseAttributeFullyQualifiedNames ( attributeFullyQualifiedNames , out var attributeData , out var attributeSyntaxNodeCandidates ) ;
28
27
29
28
return provider . CreateSyntaxProvider (
30
29
predicate : ( SyntaxNode node , CancellationToken token ) => node is BaseTypeDeclarationSyntax typeDecl && IsAnnotatedTypeDeclaration ( typeDecl , token ) ,
31
30
transform : Transform )
32
31
. Where ( ctx => ctx . Type != null )
33
32
. GroupBy (
34
- keySelector : value => value . Type ,
35
- resultSelector : static ( key , values ) =>
36
- new TypeWithAttributeDeclarationContext
37
- {
38
- TypeSymbol = ( ITypeSymbol ) key ! ,
39
- Declarations = values . Select ( v => ( v . Syntax , v . Model ) ) . ToImmutableArray ( )
33
+ keySelector : value => value . Type ,
34
+ resultSelector : static ( key , values ) =>
35
+ new TypeWithAttributeDeclarationContext
36
+ {
37
+ TypeSymbol = ( ITypeSymbol ) key ! ,
38
+ Declarations = values . Select ( v => ( v . Syntax , v . Model ) ) . ToImmutableArray ( )
40
39
} ,
41
40
42
41
keyComparer : SymbolEqualityComparer . Default ) ;
@@ -48,9 +47,12 @@ bool IsAnnotatedTypeDeclaration(BaseTypeDeclarationSyntax typeDecl, Cancellation
48
47
foreach ( AttributeSyntax attribute in attributeList . Attributes )
49
48
{
50
49
string name = GetTypeName ( attribute . Name , out int arity ) ;
51
- if ( ( name == attributeName || name == attributeNameMinusSuffix ) && arity == attributeArity )
50
+ foreach ( var candidate in attributeSyntaxNodeCandidates )
52
51
{
53
- return predicate ( typeDecl , token ) ;
52
+ if ( candidate . name == name && candidate . arity == arity )
53
+ {
54
+ return predicate ( typeDecl , token ) ;
55
+ }
54
56
}
55
57
}
56
58
}
@@ -65,12 +67,17 @@ bool IsAnnotatedTypeDeclaration(BaseTypeDeclarationSyntax typeDecl, Cancellation
65
67
66
68
foreach ( AttributeData attrData in typeSymbol . GetAttributes ( ) )
67
69
{
68
- if ( attrData . AttributeClass is INamedTypeSymbol attributeType &&
69
- attributeType . Name == attributeName &&
70
- attributeType . Arity == attributeArity &&
71
- attributeType . ContainingNamespace . MatchesNamespace ( attributeNamespace ) )
70
+ if ( attrData . AttributeClass is INamedTypeSymbol attributeType )
72
71
{
73
- return ( typeSymbol , typeDecl , ctx . SemanticModel ) ;
72
+ foreach ( var ( attributeNamespace , attributeName , attributeArity ) in attributeData )
73
+ {
74
+ if ( attributeType . Name == attributeName &&
75
+ attributeType . Arity == attributeArity &&
76
+ attributeType . ContainingNamespace . MatchesNamespace ( attributeNamespace ) )
77
+ {
78
+ return ( typeSymbol , typeDecl , ctx . SemanticModel ) ;
79
+ }
80
+ }
74
81
}
75
82
}
76
83
@@ -130,6 +137,32 @@ void Traverse(NameSyntax current)
130
137
}
131
138
}
132
139
}
140
+
141
+ static void ParseAttributeFullyQualifiedNames (
142
+ string [ ] attributeFullyQualifiedNames ,
143
+ out ( ImmutableArray < string > Namespace , string Name , int Arity ) [ ] attributeData ,
144
+ out ( string name , int arity ) [ ] attributeSyntaxNodeCandidates )
145
+ {
146
+ attributeData = new ( ImmutableArray < string > Namespace , string Name , int Arity ) [ attributeFullyQualifiedNames . Length ] ;
147
+ List < ( string name , int arity ) > attributeSyntaxNodeCandidateList = new ( ) ;
148
+ int i = 0 ;
149
+
150
+ foreach ( string attributeFqn in attributeFullyQualifiedNames )
151
+ {
152
+ NameSyntax attributeNameSyntax = SyntaxFactory . ParseName ( attributeFqn ) ;
153
+ string attributeName = GetTypeName ( attributeNameSyntax , out int attributeArity ) ;
154
+
155
+ attributeSyntaxNodeCandidateList . Add ( ( attributeName , attributeArity ) ) ;
156
+ if ( attributeName . EndsWith ( "Attribute" , StringComparison . Ordinal ) )
157
+ {
158
+ attributeSyntaxNodeCandidateList . Add ( ( attributeName [ ..^ "Attribute" . Length ] , attributeArity ) ) ;
159
+ }
160
+
161
+ attributeData [ i ++ ] = ( GetNamespaceTokens ( attributeNameSyntax ) , attributeName , attributeArity ) ;
162
+ }
163
+
164
+ attributeSyntaxNodeCandidates = attributeSyntaxNodeCandidateList . ToArray ( ) ;
165
+ }
133
166
}
134
167
135
168
// Cf. https://github.com/dotnet/roslyn/issues/72667
0 commit comments