@@ -57,7 +57,7 @@ public void Execute(GeneratorExecutionContext context)
5757 /// <param name="classDeclaration">The <see cref="ClassDeclarationSyntax"/> node to process.</param>
5858 /// <param name="classDeclarationSymbol">The <see cref="INamedTypeSymbol"/> for <paramref name="classDeclaration"/>.</param>
5959 /// <param name="items">The sequence of fields to process.</param>
60- private void OnExecute (
60+ private static void OnExecute (
6161 GeneratorExecutionContext context ,
6262 ClassDeclarationSyntax classDeclaration ,
6363 INamedTypeSymbol classDeclarationSymbol ,
@@ -127,47 +127,18 @@ private void OnExecute(
127127 /// <param name="isNotifyPropertyChanging">Indicates whether or not <see cref="INotifyPropertyChanging"/> is also implemented.</param>
128128 /// <returns>A generated <see cref="PropertyDeclarationSyntax"/> instance for the input field.</returns>
129129 [ Pure ]
130- private PropertyDeclarationSyntax CreatePropertyDeclaration ( GeneratorExecutionContext context , SyntaxTriviaList leadingTrivia , IFieldSymbol fieldSymbol , bool isNotifyPropertyChanging )
130+ private static PropertyDeclarationSyntax CreatePropertyDeclaration ( GeneratorExecutionContext context , SyntaxTriviaList leadingTrivia , IFieldSymbol fieldSymbol , bool isNotifyPropertyChanging )
131131 {
132132 // Get the field type and the target property name
133133 string
134134 typeName = fieldSymbol . Type . ToDisplayString ( SymbolDisplayFormat . FullyQualifiedFormat ) ,
135- propertyName = fieldSymbol . Name ;
136-
137- if ( propertyName . StartsWith ( "m_" ) )
138- {
139- propertyName = propertyName . Substring ( 2 ) ;
140- }
141- else if ( propertyName . StartsWith ( "_" ) )
142- {
143- propertyName = propertyName . TrimStart ( '_' ) ;
144- }
145-
146- propertyName = $ "{ char . ToUpper ( propertyName [ 0 ] ) } { propertyName . Substring ( 1 ) } ";
147-
148- BlockSyntax setter = Block ( ) ;
149-
150- // Add the OnPropertyChanging() call if necessary
151- if ( isNotifyPropertyChanging )
152- {
153- setter = setter . AddStatements ( ExpressionStatement ( InvocationExpression ( IdentifierName ( "OnPropertyChanging" ) ) ) ) ;
154- }
155-
156- // Add the following statements:
157- //
158- // <FIELD_NAME> = value;
159- // OnPropertyChanged();
160- setter = setter . AddStatements (
161- ExpressionStatement (
162- AssignmentExpression (
163- SyntaxKind . SimpleAssignmentExpression ,
164- IdentifierName ( fieldSymbol . Name ) ,
165- IdentifierName ( "value" ) ) ) ,
166- ExpressionStatement ( InvocationExpression ( IdentifierName ( "OnPropertyChanged" ) ) ) ) ;
135+ propertyName = GetGeneratedPropertyName ( fieldSymbol ) ;
167136
137+ INamedTypeSymbol observableValidatorSymbol = context . Compilation . GetTypeByMetadataName ( "Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator" ) ! ;
168138 INamedTypeSymbol alsoNotifyForAttributeSymbol = context . Compilation . GetTypeByMetadataName ( typeof ( AlsoNotifyForAttribute ) . FullName ) ! ;
169139 INamedTypeSymbol ? validationAttributeSymbol = context . Compilation . GetTypeByMetadataName ( "System.ComponentModel.DataAnnotations.ValidationAttribute" ) ;
170140
141+ List < StatementSyntax > dependentPropertyNotificationStatements = new ( ) ;
171142 List < AttributeSyntax > validationAttributes = new ( ) ;
172143
173144 foreach ( AttributeData attributeData in fieldSymbol . GetAttributes ( ) )
@@ -180,7 +151,7 @@ private PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExecutionCo
180151 if ( attributeArgument . Value is string dependentPropertyName )
181152 {
182153 // OnPropertyChanged("OtherPropertyName");
183- setter = setter . AddStatements ( ExpressionStatement (
154+ dependentPropertyNotificationStatements . Add ( ExpressionStatement (
184155 InvocationExpression ( IdentifierName ( "OnPropertyChanged" ) )
185156 . AddArgumentListArguments ( Argument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( dependentPropertyName ) ) ) ) ) ) ;
186157 }
@@ -189,28 +160,105 @@ private PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExecutionCo
189160 else if ( validationAttributeSymbol is not null &&
190161 attributeData . AttributeClass ? . InheritsFrom ( validationAttributeSymbol ) == true )
191162 {
163+ // Track the current validation attribute
192164 validationAttributes . Add ( attributeData . AsAttributeSyntax ( ) ) ;
193165 }
194166 }
195167
168+ BlockSyntax setterBlock ;
169+
170+ if ( validationAttributes . Count > 0 )
171+ {
172+ // Generate the inner setter block as follows:
173+ //
174+ // if (SetProperty(ref <FIELD_NAME>, value, true))
175+ // {
176+ // OnPropertyChanged("Property1"); // Optional
177+ // OnPropertyChanged("Property2");
178+ // ...
179+ // OnPropertyChanged("PropertyN");
180+ // }
181+ setterBlock = Block (
182+ IfStatement (
183+ InvocationExpression ( IdentifierName ( "SetProperty" ) )
184+ . AddArgumentListArguments (
185+ Argument ( IdentifierName ( fieldSymbol . Name ) ) . WithRefOrOutKeyword ( Token ( SyntaxKind . RefKeyword ) ) ,
186+ Argument ( IdentifierName ( "value" ) ) ,
187+ Argument ( LiteralExpression ( SyntaxKind . TrueLiteralExpression ) ) ) ,
188+ Block ( dependentPropertyNotificationStatements ) ) ) ;
189+ }
190+ else
191+ {
192+ BlockSyntax updateAndNotificationBlock = Block ( ) ;
193+
194+ // Add the OnPropertyChanging() call if necessary
195+ if ( isNotifyPropertyChanging )
196+ {
197+ updateAndNotificationBlock = updateAndNotificationBlock . AddStatements ( ExpressionStatement ( InvocationExpression ( IdentifierName ( "OnPropertyChanging" ) ) ) ) ;
198+ }
199+
200+ // Add the following statements:
201+ //
202+ // <FIELD_NAME> = value;
203+ // OnPropertyChanged();
204+ updateAndNotificationBlock = updateAndNotificationBlock . AddStatements (
205+ ExpressionStatement (
206+ AssignmentExpression (
207+ SyntaxKind . SimpleAssignmentExpression ,
208+ IdentifierName ( fieldSymbol . Name ) ,
209+ IdentifierName ( "value" ) ) ) ,
210+ ExpressionStatement ( InvocationExpression ( IdentifierName ( "OnPropertyChanged" ) ) ) ) ;
211+
212+ // Add the dependent property notifications at the end
213+ updateAndNotificationBlock = updateAndNotificationBlock . AddStatements ( dependentPropertyNotificationStatements . ToArray ( ) ) ;
214+
215+ // Generate the inner setter block as follows:
216+ //
217+ // if (!global::System.Collections.Generic.EqualityComparer<<FIELD_TYPE>>.Default.Equals(<FIELD_NAME>, value))
218+ // {
219+ // OnPropertyChanging(); // Optional
220+ // <FIELD_NAME> = value;
221+ // OnPropertyChanged();
222+ // OnPropertyChanged("Property1"); // Optional
223+ // OnPropertyChanged("Property2");
224+ // ...
225+ // OnPropertyChanged("PropertyN");
226+ // }
227+ setterBlock = Block (
228+ IfStatement (
229+ PrefixUnaryExpression (
230+ SyntaxKind . LogicalNotExpression ,
231+ InvocationExpression (
232+ MemberAccessExpression (
233+ SyntaxKind . SimpleMemberAccessExpression ,
234+ MemberAccessExpression (
235+ SyntaxKind . SimpleMemberAccessExpression ,
236+ GenericName ( Identifier ( "global::System.Collections.Generic.EqualityComparer" ) )
237+ . AddTypeArgumentListArguments ( IdentifierName ( typeName ) ) ,
238+ IdentifierName ( "Default" ) ) ,
239+ IdentifierName ( "Equals" ) ) )
240+ . AddArgumentListArguments (
241+ Argument ( IdentifierName ( fieldSymbol . Name ) ) ,
242+ Argument ( IdentifierName ( "value" ) ) ) ) ,
243+ updateAndNotificationBlock ) ) ;
244+ }
245+
196246 // Construct the generated property as follows:
197247 //
198248 // <FIELD_TRIVIA>
199249 // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
200250 // [global::System.Diagnostics.DebuggerNonUserCode]
201251 // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
202- // <VALIDATION_ATTRIBUTES> // Optional
252+ // <VALIDATION_ATTRIBUTE1> // Optional
253+ // <VALIDATION_ATTRIBUTE2>
254+ // ...
255+ // <VALIDATION_ATTRIBUTEN>
203256 // public <FIELD_TYPE> <PROPERTY_NAME>
204257 // {
205258 // get => <FIELD_NAME>;
206259 // set
207260 // {
208- // if (!global::System.Collections.Generic.EqualityComparer<<FIELD_TYPE>>.Default.Equals(<FIELD_NAME>, value))
209- // {
210- // OnPropertyChanging(); // Optional
211- // <FIELD_NAME> = value;
212- // OnPropertyChanged();
213- // }
261+ // <BODY>
214262 // }
215263 // }
216264 return
@@ -219,8 +267,8 @@ private PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExecutionCo
219267 AttributeList ( SingletonSeparatedList (
220268 Attribute ( IdentifierName ( $ "global::System.CodeDom.Compiler.GeneratedCode") )
221269 . AddArgumentListArguments (
222- AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( GetType ( ) . FullName ) ) ) ,
223- AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( GetType ( ) . Assembly . GetName ( ) . Version . ToString ( ) ) ) ) ) ) ) ,
270+ AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( typeof ( ObservablePropertyGenerator ) . FullName ) ) ) ,
271+ AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( typeof ( ObservablePropertyGenerator ) . Assembly . GetName ( ) . Version . ToString ( ) ) ) ) ) ) ) ,
224272 AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.DebuggerNonUserCode" ) ) ) ) ,
225273 AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage" ) ) ) ) )
226274 . AddAttributeLists ( validationAttributes . Select ( static a => AttributeList ( SingletonSeparatedList ( a ) ) ) . ToArray ( ) )
@@ -231,23 +279,29 @@ private PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExecutionCo
231279 . WithExpressionBody ( ArrowExpressionClause ( IdentifierName ( fieldSymbol . Name ) ) )
232280 . WithSemicolonToken ( Token ( SyntaxKind . SemicolonToken ) ) ,
233281 AccessorDeclaration ( SyntaxKind . SetAccessorDeclaration )
234- . AddBodyStatements (
235- IfStatement (
236- PrefixUnaryExpression (
237- SyntaxKind . LogicalNotExpression ,
238- InvocationExpression (
239- MemberAccessExpression (
240- SyntaxKind . SimpleMemberAccessExpression ,
241- MemberAccessExpression (
242- SyntaxKind . SimpleMemberAccessExpression ,
243- GenericName ( Identifier ( "global::System.Collections.Generic.EqualityComparer" ) )
244- . AddTypeArgumentListArguments ( IdentifierName ( typeName ) ) ,
245- IdentifierName ( "Default" ) ) ,
246- IdentifierName ( "Equals" ) ) )
247- . AddArgumentListArguments (
248- Argument ( IdentifierName ( fieldSymbol . Name ) ) ,
249- Argument ( IdentifierName ( "value" ) ) ) ) ,
250- setter ) ) ) ;
282+ . WithBody ( setterBlock ) ) ;
283+ }
284+
285+ /// <summary>
286+ /// Get the generated property name for an input field.
287+ /// </summary>
288+ /// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
289+ /// <returns>The generated property name for <paramref name="fieldSymbol"/>.</returns>
290+ [ Pure ]
291+ private static string GetGeneratedPropertyName ( IFieldSymbol fieldSymbol )
292+ {
293+ string propertyName = fieldSymbol . Name ;
294+
295+ if ( propertyName . StartsWith ( "m_" ) )
296+ {
297+ propertyName = propertyName . Substring ( 2 ) ;
298+ }
299+ else if ( propertyName . StartsWith ( "_" ) )
300+ {
301+ propertyName = propertyName . TrimStart ( '_' ) ;
302+ }
303+
304+ return $ "{ char . ToUpper ( propertyName [ 0 ] ) } { propertyName . Substring ( 1 ) } ";
251305 }
252306 }
253307}
0 commit comments