7
7
using System . Diagnostics . CodeAnalysis ;
8
8
using System . Globalization ;
9
9
using System . Linq ;
10
+ using System . Threading ;
11
+ using CommunityToolkit . Mvvm . SourceGenerators . ComponentModel . Models ;
10
12
using CommunityToolkit . Mvvm . SourceGenerators . Extensions ;
11
13
using CommunityToolkit . Mvvm . SourceGenerators . Helpers ;
12
14
using CommunityToolkit . Mvvm . SourceGenerators . Input . Models ;
@@ -32,12 +34,16 @@ internal static class Execute
32
34
/// </summary>
33
35
/// <param name="methodSymbol">The input <see cref="IMethodSymbol"/> instance to process.</param>
34
36
/// <param name="attributeData">The <see cref="AttributeData"/> instance the method was annotated with.</param>
37
+ /// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
38
+ /// <param name="token">The cancellation token for the current operation.</param>
35
39
/// <param name="commandInfo">The resulting <see cref="CommandInfo"/> instance, if successfully generated.</param>
36
40
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
37
41
/// <returns>Whether a <see cref="CommandInfo"/> instance could be generated successfully.</returns>
38
42
public static bool TryGetInfo (
39
43
IMethodSymbol methodSymbol ,
40
44
AttributeData attributeData ,
45
+ SemanticModel semanticModel ,
46
+ CancellationToken token ,
41
47
[ NotNullWhen ( true ) ] out CommandInfo ? commandInfo ,
42
48
out ImmutableArray < DiagnosticInfo > diagnostics )
43
49
{
@@ -113,6 +119,15 @@ public static bool TryGetInfo(
113
119
goto Failure ;
114
120
}
115
121
122
+ // Get all forwarded attributes (don't stop in case of errors, just ignore faulting attributes)
123
+ GatherForwardedAttributes (
124
+ methodSymbol ,
125
+ semanticModel ,
126
+ token ,
127
+ in builder ,
128
+ out ImmutableArray < AttributeInfo > fieldAttributes ,
129
+ out ImmutableArray < AttributeInfo > propertyAttributes ) ;
130
+
116
131
commandInfo = new CommandInfo (
117
132
methodSymbol . Name ,
118
133
fieldName ,
@@ -126,7 +141,9 @@ public static bool TryGetInfo(
126
141
canExecuteExpressionType ,
127
142
allowConcurrentExecutions ,
128
143
flowExceptionsToTaskScheduler ,
129
- generateCancelCommand ) ;
144
+ generateCancelCommand ,
145
+ fieldAttributes ,
146
+ propertyAttributes ) ;
130
147
131
148
diagnostics = builder . ToImmutable ( ) ;
132
149
@@ -160,10 +177,23 @@ public static ImmutableArray<MemberDeclarationSyntax> GetSyntax(CommandInfo comm
160
177
? commandInfo . DelegateType
161
178
: $ "{ commandInfo . DelegateType } <{ string . Join ( ", " , commandInfo . DelegateTypeArguments ) } >";
162
179
180
+ // Prepare the forwarded field attributes, if any
181
+ ImmutableArray < AttributeListSyntax > forwardedFieldAttributes =
182
+ commandInfo . ForwardedFieldAttributes
183
+ . Select ( static a => AttributeList ( SingletonSeparatedList ( a . GetSyntax ( ) ) ) )
184
+ . ToImmutableArray ( ) ;
185
+
186
+ // Also prepare any forwarded property attributes
187
+ ImmutableArray < AttributeListSyntax > forwardedPropertyAttributes =
188
+ commandInfo . ForwardedPropertyAttributes
189
+ . Select ( static a => AttributeList ( SingletonSeparatedList ( a . GetSyntax ( ) ) ) )
190
+ . ToImmutableArray ( ) ;
191
+
163
192
// Construct the generated field as follows:
164
193
//
165
194
// /// <summary>The backing field for <see cref="<COMMAND_PROPERTY_NAME>"/></summary>
166
195
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
196
+ // <FORWARDED_ATTRIBUTES>
167
197
// private <COMMAND_TYPE>? <COMMAND_FIELD_NAME>;
168
198
FieldDeclarationSyntax fieldDeclaration =
169
199
FieldDeclaration (
@@ -176,7 +206,8 @@ public static ImmutableArray<MemberDeclarationSyntax> GetSyntax(CommandInfo comm
176
206
. AddArgumentListArguments (
177
207
AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( typeof ( RelayCommandGenerator ) . FullName ) ) ) ,
178
208
AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( typeof ( RelayCommandGenerator ) . Assembly . GetName ( ) . Version . ToString ( ) ) ) ) ) ) )
179
- . WithOpenBracketToken ( Token ( TriviaList ( Comment ( $ "/// <summary>The backing field for <see cref=\" { commandInfo . PropertyName } \" />.</summary>") ) , SyntaxKind . OpenBracketToken , TriviaList ( ) ) ) ) ;
209
+ . WithOpenBracketToken ( Token ( TriviaList ( Comment ( $ "/// <summary>The backing field for <see cref=\" { commandInfo . PropertyName } \" />.</summary>") ) , SyntaxKind . OpenBracketToken , TriviaList ( ) ) ) )
210
+ . AddAttributeLists ( forwardedFieldAttributes . ToArray ( ) ) ;
180
211
181
212
// Prepares the argument to pass the underlying method to invoke
182
213
using ImmutableArrayBuilder < ArgumentSyntax > commandCreationArguments = ImmutableArrayBuilder < ArgumentSyntax > . Rent ( ) ;
@@ -265,6 +296,7 @@ public static ImmutableArray<MemberDeclarationSyntax> GetSyntax(CommandInfo comm
265
296
// /// <summary>Gets an <see cref="<COMMAND_INTERFACE_TYPE>" instance wrapping <see cref="<METHOD_NAME>"/> and <see cref="<OPTIONAL_CAN_EXECUTE>"/>.</summary>
266
297
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
267
298
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
299
+ // <FORWARDED_ATTRIBUTES>
268
300
// public <COMMAND_TYPE> <COMMAND_PROPERTY_NAME> => <COMMAND_FIELD_NAME> ??= new <RELAY_COMMAND_TYPE>(<COMMAND_CREATION_ARGUMENTS>);
269
301
PropertyDeclarationSyntax propertyDeclaration =
270
302
PropertyDeclaration (
@@ -282,6 +314,7 @@ public static ImmutableArray<MemberDeclarationSyntax> GetSyntax(CommandInfo comm
282
314
SyntaxKind . OpenBracketToken ,
283
315
TriviaList ( ) ) ) ,
284
316
AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage" ) ) ) ) )
317
+ . AddAttributeLists ( forwardedPropertyAttributes . ToArray ( ) )
285
318
. WithExpressionBody (
286
319
ArrowExpressionClause (
287
320
AssignmentExpression (
@@ -898,5 +931,75 @@ private static bool TryGetCanExecuteMemberFromGeneratedProperty(
898
931
899
932
return false ;
900
933
}
934
+
935
+ /// <summary>
936
+ /// Gathers all forwarded attributes for the generated field and property.
937
+ /// </summary>
938
+ /// <param name="methodSymbol">The input <see cref="IMethodSymbol"/> instance to process.</param>
939
+ /// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
940
+ /// <param name="token">The cancellation token for the current operation.</param>
941
+ /// <param name="diagnostics">The current collection of gathered diagnostics.</param>
942
+ /// <param name="fieldAttributes">The resulting field attributes to forward.</param>
943
+ /// <param name="propertyAttributes">The resulting property attributes to forward.</param>
944
+ private static void GatherForwardedAttributes (
945
+ IMethodSymbol methodSymbol ,
946
+ SemanticModel semanticModel ,
947
+ CancellationToken token ,
948
+ in ImmutableArrayBuilder < DiagnosticInfo > diagnostics ,
949
+ out ImmutableArray < AttributeInfo > fieldAttributes ,
950
+ out ImmutableArray < AttributeInfo > propertyAttributes )
951
+ {
952
+ using ImmutableArrayBuilder < AttributeInfo > fieldAttributesInfo = ImmutableArrayBuilder < AttributeInfo > . Rent ( ) ;
953
+ using ImmutableArrayBuilder < AttributeInfo > propertyAttributesInfo = ImmutableArrayBuilder < AttributeInfo > . Rent ( ) ;
954
+
955
+ foreach ( SyntaxReference syntaxReference in methodSymbol . DeclaringSyntaxReferences )
956
+ {
957
+ // Try to get the target method declaration syntax node
958
+ if ( syntaxReference . GetSyntax ( token ) is not MethodDeclarationSyntax methodDeclaration )
959
+ {
960
+ continue ;
961
+ }
962
+
963
+ // Gather explicit forwarded attributes info
964
+ foreach ( AttributeListSyntax attributeList in methodDeclaration . AttributeLists )
965
+ {
966
+ // Same as in the [ObservableProperty] generator, except we're also looking for fields here
967
+ if ( attributeList . Target ? . Identifier . Kind ( ) is not ( SyntaxKind . PropertyKeyword or SyntaxKind . FieldKeyword ) )
968
+ {
969
+ continue ;
970
+ }
971
+
972
+ foreach ( AttributeSyntax attribute in attributeList . Attributes )
973
+ {
974
+ // Get the symbol info for the attribute (once again just like in the [ObservableProperty] generator)
975
+ if ( ! semanticModel . GetSymbolInfo ( attribute , token ) . TryGetAttributeTypeSymbol ( out INamedTypeSymbol ? attributeTypeSymbol ) )
976
+ {
977
+ diagnostics . Add (
978
+ InvalidFieldOrPropertyTargetedAttributeOnRelayCommandMethod ,
979
+ attribute ,
980
+ methodSymbol ,
981
+ attribute . Name ) ;
982
+
983
+ continue ;
984
+ }
985
+
986
+ AttributeInfo attributeInfo = AttributeInfo . From ( attributeTypeSymbol , semanticModel , attribute . ArgumentList ? . Arguments ?? Enumerable . Empty < AttributeArgumentSyntax > ( ) , token ) ;
987
+
988
+ // Add the new attribute info to the right builder
989
+ if ( attributeList . Target ? . Identifier . Kind ( ) is SyntaxKind . FieldKeyword )
990
+ {
991
+ fieldAttributesInfo . Add ( attributeInfo ) ;
992
+ }
993
+ else
994
+ {
995
+ propertyAttributesInfo . Add ( attributeInfo ) ;
996
+ }
997
+ }
998
+ }
999
+ }
1000
+
1001
+ fieldAttributes = fieldAttributesInfo . ToImmutable ( ) ;
1002
+ propertyAttributes = propertyAttributesInfo . ToImmutable ( ) ;
1003
+ }
901
1004
}
902
1005
}
0 commit comments