mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2025-04-10 11:15:45 +02:00
Merge pull request #106 from CommunityToolkit/dev/icommand-attribute-generated-can-execute
Allow using [ICommand(CanExecute)] on generated observable properties
This commit is contained in:
commit
0936b58854
CommunityToolkit.Mvvm.SourceGenerators/Input
tests/CommunityToolkit.Mvvm.UnitTests
@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
|
||||
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
|
||||
using CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
|
||||
@ -435,6 +436,14 @@ private static bool TryGetCanExecuteExpressionType(
|
||||
|
||||
if (canExecuteSymbols.IsEmpty)
|
||||
{
|
||||
// Special case for when the target member is a generated property from [ObservableProperty]
|
||||
if (TryGetCanExecuteMemberFromGeneratedProperty(memberName, methodSymbol.ContainingType, commandTypeArguments, out canExecuteExpressionType))
|
||||
{
|
||||
canExecuteMemberName = memberName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
diagnostics.Add(InvalidCanExecuteMemberName, methodSymbol, memberName, methodSymbol.ContainingType);
|
||||
}
|
||||
else if (canExecuteSymbols.Length > 1)
|
||||
@ -531,5 +540,63 @@ private static bool TryGetCanExecuteExpressionFromSymbol(
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expression type for the can execute logic, if possible.
|
||||
/// </summary>
|
||||
/// <param name="memberName">The member name passed to <c>[ICommand(CanExecute = ...)]</c>.</param>
|
||||
/// <param name="containingType">The containing type for the method annotated with <c>[ICommand]</c>.</param>
|
||||
/// <param name="commandTypeArguments">The type arguments for the command interface, if any.</param>
|
||||
/// <param name="canExecuteExpressionType">The resulting can execute expression type, if available.</param>
|
||||
/// <returns>Whether or not <paramref name="canExecuteExpressionType"/> was set and the input symbol was valid.</returns>
|
||||
private static bool TryGetCanExecuteMemberFromGeneratedProperty(
|
||||
string memberName,
|
||||
INamedTypeSymbol containingType,
|
||||
ImmutableArray<string> commandTypeArguments,
|
||||
[NotNullWhen(true)] out CanExecuteExpressionType? canExecuteExpressionType)
|
||||
{
|
||||
foreach (ISymbol memberSymbol in containingType.GetMembers())
|
||||
{
|
||||
// Only look for instance fields of bool type
|
||||
if (memberSymbol is not IFieldSymbol fieldSymbol ||
|
||||
fieldSymbol is { IsStatic: true } ||
|
||||
!fieldSymbol.Type.HasFullyQualifiedName("bool"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ImmutableArray<AttributeData> attributes = memberSymbol.GetAttributes();
|
||||
|
||||
// Only filter fields with the [ObservableProperty] attribute
|
||||
if (memberSymbol is IFieldSymbol &&
|
||||
!attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedName(
|
||||
"global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the target property name either directly or matching the generated one
|
||||
string propertyName = ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol);
|
||||
|
||||
// If the generated property name matches, get the right expression type
|
||||
if (memberName == propertyName)
|
||||
{
|
||||
if (commandTypeArguments.Length > 0)
|
||||
{
|
||||
canExecuteExpressionType = CanExecuteExpressionType.PropertyAccessLambdaWithDiscard;
|
||||
}
|
||||
else
|
||||
{
|
||||
canExecuteExpressionType = CanExecuteExpressionType.PropertyAccessLambda;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
canExecuteExpressionType = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
@ -76,6 +77,24 @@ public void Test_ICommandAttribute_CanExecute_NoParameters_Property()
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_ICommandAttribute_CanExecute_NoParameters_GeneratedProperty()
|
||||
{
|
||||
CanExecuteViewModel model = new();
|
||||
|
||||
model.SetGeneratedFlag(true);
|
||||
|
||||
model.IncrementCounter_NoParameters_GeneratedPropertyCommand.Execute(null);
|
||||
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
|
||||
model.SetGeneratedFlag(false);
|
||||
|
||||
model.IncrementCounter_NoParameters_GeneratedPropertyCommand.Execute(null);
|
||||
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_ICommandAttribute_CanExecute_WithParameter_Property()
|
||||
{
|
||||
@ -94,6 +113,24 @@ public void Test_ICommandAttribute_CanExecute_WithParameter_Property()
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_ICommandAttribute_CanExecute_WithParameter_GeneratedProperty()
|
||||
{
|
||||
CanExecuteViewModel model = new();
|
||||
|
||||
model.SetGeneratedFlag(true);
|
||||
|
||||
model.IncrementCounter_WithParameter_GeneratedPropertyCommand.Execute(null);
|
||||
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
|
||||
model.SetGeneratedFlag(false);
|
||||
|
||||
model.IncrementCounter_WithParameter_GeneratedPropertyCommand.Execute(null);
|
||||
|
||||
Assert.AreEqual(model.Counter, 1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Test_ICommandAttribute_CanExecute_NoParameters_MethodWithNoParameters()
|
||||
{
|
||||
@ -384,12 +421,20 @@ private async Task AwaitForInputTaskAsync(Task task)
|
||||
}
|
||||
}
|
||||
|
||||
public sealed partial class CanExecuteViewModel
|
||||
public sealed partial class CanExecuteViewModel : ObservableObject
|
||||
{
|
||||
public int Counter { get; private set; }
|
||||
|
||||
public bool Flag { get; set; }
|
||||
|
||||
public void SetGeneratedFlag(bool flag)
|
||||
{
|
||||
GeneratedFlag = flag;
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
private bool generatedFlag;
|
||||
|
||||
private bool GetFlag1() => Flag;
|
||||
|
||||
private bool GetFlag2(User user) => user.Name == nameof(CanExecuteViewModel);
|
||||
@ -406,6 +451,18 @@ private void IncrementCounter_WithParameter_Property(User user)
|
||||
Counter++;
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GeneratedFlag))]
|
||||
private void IncrementCounter_NoParameters_GeneratedProperty()
|
||||
{
|
||||
Counter++;
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GeneratedFlag))]
|
||||
private void IncrementCounter_WithParameter_GeneratedProperty(User user)
|
||||
{
|
||||
Counter++;
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GetFlag1))]
|
||||
private void IncrementCounter_NoParameters_MethodWithNoParameters()
|
||||
{
|
||||
@ -440,6 +497,22 @@ private async Task IncrementCounter_Async_WithParameter_Property(User user)
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GeneratedFlag))]
|
||||
private async Task IncrementCounter_Async_NoParameters_GeneratedProperty()
|
||||
{
|
||||
Counter++;
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GeneratedFlag))]
|
||||
private async Task IncrementCounter_Async_WithParameter_GeneratedProperty(User user)
|
||||
{
|
||||
Counter++;
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
[ICommand(CanExecute = nameof(GetFlag1))]
|
||||
private async Task IncrementCounter_Async_NoParameters_MethodWithNoParameters()
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user