|
5 | 5 | using System;
|
6 | 6 | using System.Collections.Immutable;
|
7 | 7 | using System.Diagnostics.CodeAnalysis;
|
| 8 | +using System.Linq; |
8 | 9 | using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
|
9 | 10 | using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
|
10 | 11 | using CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
|
@@ -435,6 +436,14 @@ private static bool TryGetCanExecuteExpressionType(
|
435 | 436 |
|
436 | 437 | if (canExecuteSymbols.IsEmpty)
|
437 | 438 | {
|
| 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 | + |
438 | 447 | diagnostics.Add(InvalidCanExecuteMemberName, methodSymbol, memberName, methodSymbol.ContainingType);
|
439 | 448 | }
|
440 | 449 | else if (canExecuteSymbols.Length > 1)
|
@@ -531,5 +540,63 @@ private static bool TryGetCanExecuteExpressionFromSymbol(
|
531 | 540 |
|
532 | 541 | return false;
|
533 | 542 | }
|
| 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 | + } |
534 | 601 | }
|
535 | 602 | }
|
0 commit comments