Skip to content

Commit 7e0bf75

Browse files
committed
Add support for [ICommand(CanExecute)] on generated properties
1 parent ea10105 commit 7e0bf75

File tree

1 file changed

+67
-0
lines changed

1 file changed

+67
-0
lines changed

CommunityToolkit.Mvvm.SourceGenerators/Input/ICommandGenerator.Execute.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Immutable;
77
using System.Diagnostics.CodeAnalysis;
8+
using System.Linq;
89
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
910
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1011
using CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
@@ -435,6 +436,14 @@ private static bool TryGetCanExecuteExpressionType(
435436

436437
if (canExecuteSymbols.IsEmpty)
437438
{
439+
// Special case for when the target member is a generated property from [ObservableProperty]
440+
if (TryGetCanExecuteMemberFromGeneratedProperty(memberName, methodSymbol.ContainingType, commandTypeArguments, out canExecuteExpressionType))
441+
{
442+
canExecuteMemberName = memberName;
443+
444+
return true;
445+
}
446+
438447
diagnostics.Add(InvalidCanExecuteMemberName, methodSymbol, memberName, methodSymbol.ContainingType);
439448
}
440449
else if (canExecuteSymbols.Length > 1)
@@ -531,5 +540,63 @@ private static bool TryGetCanExecuteExpressionFromSymbol(
531540

532541
return false;
533542
}
543+
544+
/// <summary>
545+
/// Gets the expression type for the can execute logic, if possible.
546+
/// </summary>
547+
/// <param name="memberName">The member name passed to <c>[ICommand(CanExecute = ...)]</c>.</param>
548+
/// <param name="containingType">The containing type for the method annotated with <c>[ICommand]</c>.</param>
549+
/// <param name="commandTypeArguments">The type arguments for the command interface, if any.</param>
550+
/// <param name="canExecuteExpressionType">The resulting can execute expression type, if available.</param>
551+
/// <returns>Whether or not <paramref name="canExecuteExpressionType"/> was set and the input symbol was valid.</returns>
552+
private static bool TryGetCanExecuteMemberFromGeneratedProperty(
553+
string memberName,
554+
INamedTypeSymbol containingType,
555+
ImmutableArray<string> commandTypeArguments,
556+
[NotNullWhen(true)] out CanExecuteExpressionType? canExecuteExpressionType)
557+
{
558+
foreach (ISymbol memberSymbol in containingType.GetMembers())
559+
{
560+
// Only look for instance fields of bool type
561+
if (memberSymbol is not IFieldSymbol fieldSymbol ||
562+
fieldSymbol is { IsStatic: true } ||
563+
!fieldSymbol.Type.HasFullyQualifiedName("bool"))
564+
{
565+
continue;
566+
}
567+
568+
ImmutableArray<AttributeData> attributes = memberSymbol.GetAttributes();
569+
570+
// Only filter fields with the [ObservableProperty] attribute
571+
if (memberSymbol is IFieldSymbol &&
572+
!attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedName(
573+
"global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true))
574+
{
575+
continue;
576+
}
577+
578+
// Get the target property name either directly or matching the generated one
579+
string propertyName = ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol);
580+
581+
// If the generated property name matches, get the right expression type
582+
if (memberName == propertyName)
583+
{
584+
if (commandTypeArguments.Length > 0)
585+
{
586+
canExecuteExpressionType = CanExecuteExpressionType.PropertyAccessLambdaWithDiscard;
587+
}
588+
else
589+
{
590+
canExecuteExpressionType = CanExecuteExpressionType.PropertyAccessLambda;
591+
}
592+
593+
return true;
594+
}
595+
}
596+
597+
canExecuteExpressionType = null;
598+
599+
return false;
600+
}
534601
}
535602
}

0 commit comments

Comments
 (0)