15
15
using Microsoft . Toolkit . Mvvm . SourceGenerators . Extensions ;
16
16
using static Microsoft . CodeAnalysis . CSharp . SyntaxFactory ;
17
17
18
+ #pragma warning disable SA1008
19
+
18
20
namespace Microsoft . Toolkit . Mvvm . SourceGenerators
19
21
{
20
22
/// <summary>
@@ -39,8 +41,10 @@ public void Execute(GeneratorExecutionContext context)
39
41
return ;
40
42
}
41
43
42
- // Get the symbol for the ValidationAttribute type
43
- INamedTypeSymbol validationSymbol = context . Compilation . GetTypeByMetadataName ( "System.ComponentModel.DataAnnotations.ValidationAttribute" ) ! ;
44
+ // Get the symbol for the required attributes
45
+ INamedTypeSymbol
46
+ validationSymbol = context . Compilation . GetTypeByMetadataName ( "System.ComponentModel.DataAnnotations.ValidationAttribute" ) ! ,
47
+ observablePropertySymbol = context . Compilation . GetTypeByMetadataName ( "Microsoft.Toolkit.Mvvm.ComponentModel.ObservablePropertyAttribute" ) ! ;
44
48
45
49
// Prepare the attributes to add to the first class declaration
46
50
AttributeListSyntax [ ] classAttributes = new [ ]
@@ -138,14 +142,14 @@ public void Execute(GeneratorExecutionContext context)
138
142
Parameter ( Identifier ( "obj" ) ) . WithType ( PredefinedType ( Token ( SyntaxKind . ObjectKeyword ) ) ) )
139
143
. WithBody ( Block (
140
144
LocalDeclarationStatement (
141
- VariableDeclaration ( IdentifierName ( "var" ) ) // Cannot Token(SyntaxKind.VarKeyword) here (throws an ArgumentException)
145
+ VariableDeclaration ( IdentifierName ( "var" ) ) // Cannot use Token(SyntaxKind.VarKeyword) here (throws an ArgumentException)
142
146
. AddVariables (
143
147
VariableDeclarator ( Identifier ( "instance" ) )
144
148
. WithInitializer ( EqualsValueClause (
145
149
CastExpression (
146
150
IdentifierName ( classSymbol . ToDisplayString ( SymbolDisplayFormat . FullyQualifiedFormat ) ) ,
147
151
IdentifierName ( "obj" ) ) ) ) ) ) )
148
- . AddStatements ( EnumerateValidationStatements ( classSymbol , validationSymbol ) . ToArray ( ) ) ) ,
152
+ . AddStatements ( EnumerateValidationStatements ( classSymbol , validationSymbol , observablePropertySymbol ) . ToArray ( ) ) ) ,
149
153
ReturnStatement ( IdentifierName ( "ValidateAllProperties" ) ) ) ) ) ) )
150
154
. NormalizeWhitespace ( )
151
155
. ToFullString ( ) ;
@@ -159,28 +163,47 @@ public void Execute(GeneratorExecutionContext context)
159
163
}
160
164
161
165
/// <summary>
162
- /// Gets a sequence of statements to validate declared properties.
166
+ /// Gets a sequence of statements to validate declared properties (including generated ones) .
163
167
/// </summary>
164
168
/// <param name="classSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
165
169
/// <param name="validationSymbol">The type symbol for the <c>ValidationAttribute</c> type.</param>
170
+ /// <param name="observablePropertySymbol">The type symbol for the <c>ObservablePropertyAttribute</c> type.</param>
166
171
/// <returns>The sequence of <see cref="StatementSyntax"/> instances to validate declared properties.</returns>
167
172
[ Pure ]
168
- private static IEnumerable < StatementSyntax > EnumerateValidationStatements ( INamedTypeSymbol classSymbol , INamedTypeSymbol validationSymbol )
173
+ private static IEnumerable < StatementSyntax > EnumerateValidationStatements ( INamedTypeSymbol classSymbol , INamedTypeSymbol validationSymbol , INamedTypeSymbol observablePropertySymbol )
169
174
{
170
- foreach ( var propertySymbol in classSymbol . GetMembers ( ) . OfType < IPropertySymbol > ( ) )
175
+ foreach ( var memberSymbol in classSymbol . GetMembers ( ) )
171
176
{
172
- if ( propertySymbol . IsIndexer )
177
+ if ( memberSymbol is not ( IPropertySymbol { IsIndexer : false } or IFieldSymbol ) )
173
178
{
174
179
continue ;
175
180
}
176
181
177
- ImmutableArray < AttributeData > attributes = propertySymbol . GetAttributes ( ) ;
182
+ ImmutableArray < AttributeData > attributes = memberSymbol . GetAttributes ( ) ;
178
183
184
+ // Also include fields that are annotated with [ObservableProperty]. This is necessary because
185
+ // all generators run in an undefined order and looking at the same original compilation, so the
186
+ // current one wouldn't be able to see generated properties from other generators directly.
187
+ if ( memberSymbol is IFieldSymbol &&
188
+ ! attributes . Any ( a => SymbolEqualityComparer . Default . Equals ( a . AttributeClass , observablePropertySymbol ) ) )
189
+ {
190
+ continue ;
191
+ }
192
+
193
+ // Skip the current member if there are no validation attributes applied to it
179
194
if ( ! attributes . Any ( a => a . AttributeClass ? . InheritsFrom ( validationSymbol ) == true ) )
180
195
{
181
196
continue ;
182
197
}
183
198
199
+ // Get the target property name either directly or matching the generated one
200
+ string propertyName = memberSymbol switch
201
+ {
202
+ IPropertySymbol propertySymbol => propertySymbol . Name ,
203
+ IFieldSymbol fieldSymbol => ObservablePropertyGenerator . GetGeneratedPropertyName ( fieldSymbol ) ,
204
+ _ => throw new InvalidOperationException ( "Invalid symbol type" )
205
+ } ;
206
+
184
207
// This enumerator produces a sequence of statements as follows:
185
208
//
186
209
// __ObservableValidatorHelper.ValidateProperty(instance, instance.<PROPERTY_0>, nameof(instance.<PROPERTY_0>));
@@ -200,14 +223,14 @@ private static IEnumerable<StatementSyntax> EnumerateValidationStatements(INamed
200
223
MemberAccessExpression (
201
224
SyntaxKind . SimpleMemberAccessExpression ,
202
225
IdentifierName ( "instance" ) ,
203
- IdentifierName ( propertySymbol . Name ) ) ) ,
226
+ IdentifierName ( propertyName ) ) ) ,
204
227
Argument (
205
228
InvocationExpression ( IdentifierName ( "nameof" ) )
206
229
. AddArgumentListArguments ( Argument (
207
230
MemberAccessExpression (
208
231
SyntaxKind . SimpleMemberAccessExpression ,
209
232
IdentifierName ( "instance" ) ,
210
- IdentifierName ( propertySymbol . Name ) ) ) ) ) ) ) ;
233
+ IdentifierName ( propertyName ) ) ) ) ) ) ) ;
211
234
}
212
235
}
213
236
}
0 commit comments