Skip to content

Commit 216d4ea

Browse files
committed
Code refactoring to remove goto-s
1 parent eb41f89 commit 216d4ea

File tree

1 file changed

+93
-74
lines changed

1 file changed

+93
-74
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

Lines changed: 93 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -183,48 +183,51 @@ private static bool TryGatherDependentPropertyChangedNames(
183183
ImmutableArray<string>.Builder propertyChangedNames,
184184
ImmutableArray<Diagnostic>.Builder diagnostics)
185185
{
186-
if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyChangeForAttribute") == true)
186+
// Validates a property name using existing properties
187+
bool IsPropertyNameValid(string propertyName)
187188
{
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())
189196
{
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))
191201
{
192-
goto InvalidProperty;
202+
propertyChangedNames.Add(propertyName);
203+
204+
return true;
193205
}
206+
}
207+
208+
return false;
209+
}
194210

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)))
197220
{
198221
propertyChangedNames.Add(dependentPropertyName);
199-
200-
goto ValidProperty;
201222
}
202-
203-
// Check for generated properties too
204-
foreach (ISymbol member in fieldSymbol.ContainingType.GetMembers())
223+
else
205224
{
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);
215230
}
216-
217-
InvalidProperty:
218-
219-
diagnostics.Add(
220-
AlsoNotifyChangeForInvalidTargetError,
221-
fieldSymbol,
222-
dependentPropertyName ?? "",
223-
fieldSymbol.ContainingType);
224-
225-
ValidProperty:
226-
227-
continue;
228231
}
229232

230233
return true;
@@ -247,58 +250,74 @@ private static bool TryGatherDependentCommandNames(
247250
ImmutableArray<string>.Builder notifiedCommandNames,
248251
ImmutableArray<Diagnostic>.Builder diagnostics)
249252
{
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)
251255
{
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)
253259
{
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")))
255266
{
256-
goto InvalidProperty;
257-
}
267+
shouldLookForGeneratedMembersToo = true;
258268

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;
276270
}
277271

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;
290275

291-
InvalidProperty:
276+
return false;
277+
}
292278

293-
diagnostics.Add(
294-
AlsoNotifyCanExecuteForInvalidTargetError,
295-
fieldSymbol,
296-
commandName ?? "",
297-
fieldSymbol.ContainingType);
279+
shouldLookForGeneratedMembersToo = true;
298280

299-
ValidProperty:
281+
return false;
282+
}
300283

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+
}
302321
}
303322

304323
return true;

0 commit comments

Comments
 (0)