@@ -183,48 +183,51 @@ private static bool TryGatherDependentPropertyChangedNames(
183
183
ImmutableArray < string > . Builder propertyChangedNames ,
184
184
ImmutableArray < Diagnostic > . Builder diagnostics )
185
185
{
186
- if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyChangeForAttribute" ) == true )
186
+ // Validates a property name using existing properties
187
+ bool IsPropertyNameValid ( string propertyName )
187
188
{
188
- foreach ( string ? dependentPropertyName in attributeData . GetConstructorArguments < string > ( ) )
189
+ return fieldSymbol . ContainingType . GetMembers ( propertyName ) . OfType < IPropertySymbol > ( ) . Any ( ) ;
190
+ }
191
+
192
+ // Validate a property name including generated properties too
193
+ bool IsPropertyNameValidWithGeneratedMembers ( string propertyName )
194
+ {
195
+ foreach ( ISymbol member in fieldSymbol . ContainingType . GetMembers ( ) )
189
196
{
190
- if ( dependentPropertyName is null or "" )
197
+ if ( member is IFieldSymbol otherFieldSymbol &&
198
+ ! SymbolEqualityComparer . Default . Equals ( fieldSymbol , otherFieldSymbol ) &&
199
+ otherFieldSymbol . HasAttributeWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute" ) &&
200
+ propertyName == GetGeneratedPropertyName ( otherFieldSymbol ) )
191
201
{
192
- goto InvalidProperty ;
202
+ propertyChangedNames . Add ( propertyName ) ;
203
+
204
+ return true ;
193
205
}
206
+ }
207
+
208
+ return false ;
209
+ }
194
210
195
- // Each target must be a string matching the name of a property from the containing type of the annotated field
196
- if ( fieldSymbol . ContainingType . GetMembers ( dependentPropertyName ) . OfType < IPropertySymbol > ( ) . Any ( ) )
211
+ if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyChangeForAttribute" ) == true )
212
+ {
213
+ foreach ( string ? dependentPropertyName in attributeData . GetConstructorArguments < string > ( ) )
214
+ {
215
+ // Each target must be a (not null and not empty) string matching the name of a property from the containing type
216
+ // of the annotated field being processed, or alternatively it must match the name of a property being generated.
217
+ if ( dependentPropertyName is not ( null or "" ) &&
218
+ ( IsPropertyNameValid ( dependentPropertyName ) ||
219
+ IsPropertyNameValidWithGeneratedMembers ( dependentPropertyName ) ) )
197
220
{
198
221
propertyChangedNames . Add ( dependentPropertyName ) ;
199
-
200
- goto ValidProperty ;
201
222
}
202
-
203
- // Check for generated properties too
204
- foreach ( ISymbol member in fieldSymbol . ContainingType . GetMembers ( ) )
223
+ else
205
224
{
206
- if ( member is IFieldSymbol otherFieldSymbol &&
207
- ! SymbolEqualityComparer . Default . Equals ( fieldSymbol , otherFieldSymbol ) &&
208
- otherFieldSymbol . HasAttributeWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute" ) &&
209
- dependentPropertyName == GetGeneratedPropertyName ( otherFieldSymbol ) )
210
- {
211
- propertyChangedNames . Add ( dependentPropertyName ) ;
212
-
213
- goto ValidProperty ;
214
- }
225
+ diagnostics . Add (
226
+ AlsoNotifyChangeForInvalidTargetError ,
227
+ fieldSymbol ,
228
+ dependentPropertyName ?? "" ,
229
+ fieldSymbol . ContainingType ) ;
215
230
}
216
-
217
- InvalidProperty :
218
-
219
- diagnostics . Add (
220
- AlsoNotifyChangeForInvalidTargetError ,
221
- fieldSymbol ,
222
- dependentPropertyName ?? "" ,
223
- fieldSymbol . ContainingType ) ;
224
-
225
- ValidProperty :
226
-
227
- continue ;
228
231
}
229
232
230
233
return true ;
@@ -247,58 +250,74 @@ private static bool TryGatherDependentCommandNames(
247
250
ImmutableArray < string > . Builder notifiedCommandNames ,
248
251
ImmutableArray < Diagnostic > . Builder diagnostics )
249
252
{
250
- if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyCanExecuteForAttribute" ) == true )
253
+ // Validates a command name using existing properties
254
+ bool IsCommandNameValid ( string commandName , out bool shouldLookForGeneratedMembersToo )
251
255
{
252
- foreach ( string ? commandName in attributeData . GetConstructorArguments < string > ( ) )
256
+ // Each target must be a string matching the name of a property from the containing type of the annotated field, and the
257
+ // property must be of type IRelayCommand, or any type that implements that interface (to avoid generating invalid code).
258
+ if ( fieldSymbol . ContainingType . GetMembers ( commandName ) . OfType < IPropertySymbol > ( ) . FirstOrDefault ( ) is IPropertySymbol propertySymbol )
253
259
{
254
- if ( commandName is null or "" )
260
+ // If there is a property member with the specified name, check that it's valid. If it isn't, the
261
+ // target is definitely not valid, and the additional checks below can just be skipped. The property
262
+ // is valid if it's of type IRelayCommand, or it has IRelayCommand in the set of all interfaces.
263
+ if ( propertySymbol . Type is INamedTypeSymbol typeSymbol &&
264
+ ( typeSymbol . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.IRelayCommand" ) ||
265
+ typeSymbol . HasInterfaceWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.IRelayCommand" ) ) )
255
266
{
256
- goto InvalidProperty ;
257
- }
267
+ shouldLookForGeneratedMembersToo = true ;
258
268
259
- // Each target must be a string matching the name of a property from the containing type of the annotated field, and the
260
- // property must be of type IRelayCommand, or any type that implements that interface (to avoid generating invalid code).
261
- if ( fieldSymbol . ContainingType . GetMembers ( commandName ) . OfType < IPropertySymbol > ( ) . FirstOrDefault ( ) is IPropertySymbol propertySymbol )
262
- {
263
- // If there is a property member with the specified name, check that it's valid. If it isn't, the
264
- // target is definitely not valid, and the additional checks below can just be skipped. The property
265
- // is valid if it's of type IRelayCommand, or it has IRelayCommand in the set of all interfaces.
266
- if ( propertySymbol . Type is INamedTypeSymbol typeSymbol &&
267
- ( typeSymbol . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.IRelayCommand" ) ||
268
- typeSymbol . HasInterfaceWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.IRelayCommand" ) ) )
269
- {
270
- notifiedCommandNames . Add ( commandName ) ;
271
-
272
- goto ValidProperty ;
273
- }
274
-
275
- goto InvalidProperty ;
269
+ return true ;
276
270
}
277
271
278
- // Check for generated commands too
279
- foreach ( ISymbol member in fieldSymbol . ContainingType . GetMembers ( ) )
280
- {
281
- if ( member is IMethodSymbol methodSymbol &&
282
- methodSymbol . HasAttributeWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.ICommandAttribute" ) &&
283
- commandName == ICommandGenerator . Execute . GetGeneratedFieldAndPropertyNames ( methodSymbol ) . PropertyName )
284
- {
285
- notifiedCommandNames . Add ( commandName ) ;
286
-
287
- goto ValidProperty ;
288
- }
289
- }
272
+ // If a property with this name exists but is not valid, the search should stop immediately, as
273
+ // the target is already known not to be valid, so there is no reason to look for other members.
274
+ shouldLookForGeneratedMembersToo = false ;
290
275
291
- InvalidProperty :
276
+ return false ;
277
+ }
292
278
293
- diagnostics . Add (
294
- AlsoNotifyCanExecuteForInvalidTargetError ,
295
- fieldSymbol ,
296
- commandName ?? "" ,
297
- fieldSymbol . ContainingType ) ;
279
+ shouldLookForGeneratedMembersToo = true ;
298
280
299
- ValidProperty :
281
+ return false ;
282
+ }
300
283
301
- continue ;
284
+ // Validate a command name including generated command too
285
+ bool IsCommandNameValidWithGeneratedMembers ( string commandName )
286
+ {
287
+ foreach ( ISymbol member in fieldSymbol . ContainingType . GetMembers ( ) )
288
+ {
289
+ if ( member is IMethodSymbol methodSymbol &&
290
+ methodSymbol . HasAttributeWithFullyQualifiedName ( "global::CommunityToolkit.Mvvm.Input.ICommandAttribute" ) &&
291
+ commandName == ICommandGenerator . Execute . GetGeneratedFieldAndPropertyNames ( methodSymbol ) . PropertyName )
292
+ {
293
+ return true ;
294
+ }
295
+ }
296
+
297
+ return false ;
298
+ }
299
+
300
+ if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyCanExecuteForAttribute" ) == true )
301
+ {
302
+ foreach ( string ? commandName in attributeData . GetConstructorArguments < string > ( ) )
303
+ {
304
+ // Each command must be a (not null and not empty) string matching the name of an existing command from the containing
305
+ // type (just like for properties), or it must match a generated command. The only caveat is the case where a property
306
+ // with the requested name does exist, but it is not of the right type. In that case the search should stop immediately.
307
+ if ( commandName is not ( null or "" ) &&
308
+ ( IsCommandNameValid ( commandName , out bool shouldLookForGeneratedMembersToo ) ||
309
+ shouldLookForGeneratedMembersToo && IsCommandNameValidWithGeneratedMembers ( commandName ) ) )
310
+ {
311
+ notifiedCommandNames . Add ( commandName ) ;
312
+ }
313
+ else
314
+ {
315
+ diagnostics . Add (
316
+ AlsoNotifyCanExecuteForInvalidTargetError ,
317
+ fieldSymbol ,
318
+ commandName ?? "" ,
319
+ fieldSymbol . ContainingType ) ;
320
+ }
302
321
}
303
322
304
323
return true ;
0 commit comments