6
6
using System . ComponentModel ;
7
7
using System . Globalization ;
8
8
using System . Linq ;
9
+ using System . Threading ;
9
10
using CommunityToolkit . Mvvm . SourceGenerators . ComponentModel . Models ;
10
11
using CommunityToolkit . Mvvm . SourceGenerators . Diagnostics ;
11
12
using CommunityToolkit . Mvvm . SourceGenerators . Extensions ;
@@ -28,10 +29,18 @@ internal static class Execute
28
29
/// <summary>
29
30
/// Processes a given field.
30
31
/// </summary>
32
+ /// <param name="fieldSyntax">The <see cref="FieldDeclarationSyntax"/> instance to process.</param>
31
33
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
34
+ /// <param name="semanticModel">The <see cref="SemanticModel"/> instance for the current run.</param>
35
+ /// <param name="token">The cancellation token for the current operation.</param>
32
36
/// <param name="diagnostics">The resulting diagnostics from the processing operation.</param>
33
37
/// <returns>The resulting <see cref="PropertyInfo"/> instance for <paramref name="fieldSymbol"/>, if successful.</returns>
34
- public static PropertyInfo ? TryGetInfo ( IFieldSymbol fieldSymbol , out ImmutableArray < Diagnostic > diagnostics )
38
+ public static PropertyInfo ? TryGetInfo (
39
+ FieldDeclarationSyntax fieldSyntax ,
40
+ IFieldSymbol fieldSymbol ,
41
+ SemanticModel semanticModel ,
42
+ CancellationToken token ,
43
+ out ImmutableArray < Diagnostic > diagnostics )
35
44
{
36
45
ImmutableArray < Diagnostic > . Builder builder = ImmutableArray . CreateBuilder < Diagnostic > ( ) ;
37
46
@@ -168,6 +177,45 @@ internal static class Execute
168
177
}
169
178
}
170
179
180
+ // Gather explicit forwarded attributes info
181
+ foreach ( AttributeListSyntax attributeList in fieldSyntax . AttributeLists )
182
+ {
183
+ // Only look for attribute lists explicitly targeting the (generated) property. Roslyn will normally emit a
184
+ // CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic suppressor
185
+ // that recognizes uses of this target specifically to support [ObservableProperty].
186
+ if ( attributeList . Target ? . Identifier . Kind ( ) is not SyntaxKind . PropertyKeyword )
187
+ {
188
+ continue ;
189
+ }
190
+
191
+ foreach ( AttributeSyntax attribute in attributeList . Attributes )
192
+ {
193
+ // Roslyn ignores attributes in an attribute list with an invalid target, so we can't get the AttributeData as usual.
194
+ // To reconstruct all necessary attribute info to generate the serialized model, we use the following steps:
195
+ // - We try to get the attribute symbol from the semantic model, for the current attribute syntax. In case this is not
196
+ // available (in theory it shouldn't, but it can be), we try to get it from the candidate symbols list for the node.
197
+ // If there are no candidates or more than one, we just issue a diagnostic and stop processing the current attribute.
198
+ // The returned symbols might be method symbols (constructor attribute) so in that case we can get the declaring type.
199
+ // - We then go over each attribute argument expression and get the operation for it. This will still be available even
200
+ // though the rest of the attribute is not validated nor bound at all. From the operation we can still retrieve all
201
+ // constant values to build the AttributeInfo model. After all, attributes only support constant values, typeof(T)
202
+ // expressions, or arrays of either these two types, or of other arrays with the same rules, recursively.
203
+ // - From the syntax, we can also determine the identifier names for named attribute arguments, if any.
204
+ // There is no need to validate anything here: the attribute will be forwarded as is, and then Roslyn will validate on the
205
+ // generated property. Users will get the same validation they'd have had directly over the field. The only drawback is the
206
+ // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway.
207
+ SymbolInfo attributeSymbolInfo = semanticModel . GetSymbolInfo ( attribute , token ) ;
208
+
209
+ if ( ( attributeSymbolInfo . Symbol ?? attributeSymbolInfo . CandidateSymbols . SingleOrDefault ( ) ) is not ISymbol attributeSymbol ||
210
+ ( attributeSymbol as INamedTypeSymbol ?? attributeSymbol . ContainingType ) is not INamedTypeSymbol attributeTypeSymbol )
211
+ {
212
+ continue ;
213
+ }
214
+
215
+ forwardedAttributes . Add ( AttributeInfo . From ( attributeTypeSymbol , semanticModel , attribute . ArgumentList ? . Arguments ?? Enumerable . Empty < AttributeArgumentSyntax > ( ) , token ) ) ;
216
+ }
217
+ }
218
+
171
219
// Log the diagnostic for missing ObservableValidator, if needed
172
220
if ( hasAnyValidationAttributes &&
173
221
! fieldSymbol . ContainingType . InheritsFromFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator" ) )
0 commit comments