diff --git a/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md b/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md index 8a0b4bea..8f3725fc 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md @@ -30,3 +30,4 @@ MVVMTK0020 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator MVVMTK0021 | CommunityToolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit/error MVVMTK0022 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error MVVMTK0023 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error +MVVMTK0024 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 0e99c852..ed52efe5 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -71,6 +71,20 @@ internal static class Execute return null; } + // Check for special cases that are explicitly not allowed + if (IsGeneratedPropertyInvalid(propertyName, fieldSymbol.Type)) + { + builder.Add( + InvalidObservablePropertyError, + fieldSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name); + + diagnostics = builder.ToImmutable(); + + return null; + } + ImmutableArray.Builder propertyChangedNames = ImmutableArray.CreateBuilder(); ImmutableArray.Builder propertyChangingNames = ImmutableArray.CreateBuilder(); ImmutableArray.Builder notifiedCommandNames = ImmutableArray.CreateBuilder(); @@ -178,6 +192,29 @@ private static bool IsTargetTypeValid( return isObservableObject || hasObservableObjectAttribute || hasINotifyPropertyChangedAttribute; } + /// + /// Checks whether the generated property would be a special case that is marked as invalid. + /// + /// The property name. + /// The property type. + /// Whether the generated property is invalid. + private static bool IsGeneratedPropertyInvalid(string propertyName, ITypeSymbol propertyType) + { + // If the generated property name is called "Property" and the type is either object or it is PropertyChangedEventArgs or + // PropertyChangingEventArgs (or a type derived from either of those two types), consider it invalid. This is needed because + // if such a property was generated, the partial OnChanging and OnPropertyChanging(PropertyChangingEventArgs) + // methods, as well as the partial OnChanged and OnPropertyChanged(PropertyChangedEventArgs) methods. + if (propertyName == "Property") + { + return + propertyType.SpecialType == SpecialType.System_Object || + propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangedEventArgs") || + propertyType.HasOrInheritsFromFullyQualifiedName("global::System.ComponentModel.PropertyChangingEventArgs"); + } + + return false; + } + /// /// Tries to gather dependent properties from the given attribute. /// diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 6cfe5fd4..613bd751 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -379,4 +379,20 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, description: "Methods with multiple overloads cannot be annotated with [ICommand], as command methods must be unique within their containing type.", helpLinkUri: "https://aka.ms/mvvmtoolkit"); + + /// + /// Gets a indicating when a generated property created with [ObservableProperty] would cause conflicts with other generated members. + /// + /// Format: "The field {0}.{1} cannot be used to generate an observable property, as its name or type would cause conflicts with other generated members". + /// + /// + public static readonly DiagnosticDescriptor InvalidObservablePropertyError = new DiagnosticDescriptor( + id: "MVVMTK0024", + title: "Invalid generated property declaration", + messageFormat: "The field {0}.{1} cannot be used to generate an observable property, as its name or type would cause conflicts with other generated members", + category: typeof(ObservablePropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "The fields annotated with [ObservableProperty] cannot result in a property name or have a type that would cause conflicts with other generated members.", + helpLinkUri: "https://aka.ms/mvvmtoolkit"); } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index b2c44e72..d1762224 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -13,6 +13,25 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// internal static class ITypeSymbolExtensions { + /// + /// Checks whether or not a given has or inherits from a specified type. + /// + /// The target instance to check. + /// The full name of the type to check for inheritance. + /// Whether or not is or inherits from . + public static bool HasOrInheritsFromFullyQualifiedName(this ITypeSymbol typeSymbol, string name) + { + for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType) + { + if (currentType.HasFullyQualifiedName(name)) + { + return true; + } + } + + return false; + } + /// /// Checks whether or not a given inherits from a specified type. /// diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index 894a1af7..14d087d1 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -1105,6 +1105,89 @@ private void GreetUser(object value) VerifyGeneratedDiagnostics(source, "MVVMTK0023"); } + [TestMethod] + public void InvalidObservablePropertyError_Object() + { + string source = @" + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + public object property; + } + }"; + + VerifyGeneratedDiagnostics(source, "MVVMTK0024"); + } + + [TestMethod] + public void InvalidObservablePropertyError_PropertyChangingEventArgs() + { + string source = @" + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + public PropertyChangingEventArgs property; + } + }"; + + VerifyGeneratedDiagnostics(source, "MVVMTK0024"); + } + + [TestMethod] + public void InvalidObservablePropertyError_PropertyChangedEventArgs() + { + string source = @" + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + public PropertyChangedEventArgs property; + } + }"; + + VerifyGeneratedDiagnostics(source, "MVVMTK0024"); + } + + [TestMethod] + public void InvalidObservablePropertyError_CustomTypeDerivedFromPropertyChangedEventArgs() + { + string source = @" + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public class MyPropertyChangedEventArgs : PropertyChangedEventArgs + { + public MyPropertyChangedEventArgs(string propertyName) + : base(propertyName) + { + } + } + + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + public MyPropertyChangedEventArgs property; + } + }"; + + VerifyGeneratedDiagnostics(source, "MVVMTK0024"); + } + /// /// Verifies the output of a source generator. ///