1
0
mirror of https://github.com/chylex/.NET-Community-Toolkit.git synced 2025-04-10 11:15:45 +02:00

Merge pull request from CommunityToolkit/dev/icommand-attribute-generated-can-execute

Allow using [ICommand(CanExecute)] on generated observable properties
This commit is contained in:
Sergio Pedri 2022-01-31 22:24:55 +01:00 committed by GitHub
commit 0936b58854
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 141 additions and 1 deletions
CommunityToolkit.Mvvm.SourceGenerators/Input
tests/CommunityToolkit.Mvvm.UnitTests

View File

@ -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;
}
}
}

View File

@ -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()
{