diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs index 9a8bccbd..dca6a595 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs @@ -33,14 +33,12 @@ public INotifyPropertyChangedGenerator() { diagnostics = ImmutableArray.Empty; - INotifyPropertyChangedInfo? info = null; - // Check if the type already implements INotifyPropertyChanged - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanged"))) + if (typeSymbol.ImplementsInterfaceMember("System.ComponentModel.INotifyPropertyChanged")) { diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol)); - goto End; + return null; } // Check if the type uses [INotifyPropertyChanged] or [ObservableObject] already (in the type hierarchy too) @@ -49,15 +47,12 @@ public INotifyPropertyChangedGenerator() { diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(InvalidAttributeCombinationForINotifyPropertyChangedAttributeError, typeSymbol, typeSymbol)); - goto End; + return null; } bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true); - info = new INotifyPropertyChangedInfo(includeAdditionalHelperMethods); - - End: - return info; + return new INotifyPropertyChangedInfo(includeAdditionalHelperMethods); } /// diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs index a2f4bff2..a593e8a8 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs @@ -2,12 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using CommunityToolkit.Mvvm.SourceGenerators.Models; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace CommunityToolkit.Mvvm.SourceGenerators; @@ -32,7 +31,7 @@ private protected override int ValidateTargetTypeAndGetInfo(INamedTypeSymbol typ diagnostics = ImmutableArray.Empty; // Check if the type already implements INotifyPropertyChanged... - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanged"))) + if (typeSymbol.ImplementsInterfaceMember("System.ComponentModel.INotifyPropertyChanged")) { diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol)); @@ -40,7 +39,7 @@ private protected override int ValidateTargetTypeAndGetInfo(INamedTypeSymbol typ } // ...or INotifyPropertyChanging - if (typeSymbol.AllInterfaces.Any(i => i.HasFullyQualifiedMetadataName("System.ComponentModel.INotifyPropertyChanging"))) + if (typeSymbol.ImplementsInterfaceMember("System.ComponentModel.INotifyPropertyChanging")) { diagnostics = ImmutableArray.Create(DiagnosticInfo.Create(DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError, typeSymbol, typeSymbol)); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 431e5da4..6d5d598c 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -35,51 +35,51 @@ internal static class DiagnosticDescriptors public const string AsyncVoidReturningRelayCommandMethodId = "MVVMTK0039"; /// - /// Gets a indicating when a duplicate declaration of would happen. + /// Gets a indicating when a duplicate implementation of would happen. /// - /// Format: "Cannot apply [INotifyPropertyChangedAttribute] to type {0}, as it already declares the INotifyPropertyChanged interface". + /// Format: "Cannot apply [INotifyPropertyChangedAttribute] to type {0}, as it already implements the INotifyPropertyChanged interface". /// /// public static readonly DiagnosticDescriptor DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError = new DiagnosticDescriptor( id: "MVVMTK0001", title: $"Duplicate {nameof(INotifyPropertyChanged)} definition", - messageFormat: $"Cannot apply [INotifyPropertyChanged] to type {{0}}, as it already declares the {nameof(INotifyPropertyChanged)} interface", + messageFormat: $"Cannot apply [INotifyPropertyChanged] to type {{0}}, as it already implements the {nameof(INotifyPropertyChanged)} interface", category: typeof(INotifyPropertyChangedGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, - description: $"Cannot apply [INotifyPropertyChanged] to a type that already declares the {nameof(INotifyPropertyChanged)} interface.", + description: $"Cannot apply [INotifyPropertyChanged] to a type that already implements the {nameof(INotifyPropertyChanged)} interface.", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0001"); /// - /// Gets a indicating when a duplicate declaration of would happen. + /// Gets a indicating when a duplicate implementation of would happen. /// - /// Format: "Cannot apply [ObservableObjectAttribute] to type {0}, as it already declares the INotifyPropertyChanged interface". + /// Format: "Cannot apply [ObservableObjectAttribute] to type {0}, as it already implement the INotifyPropertyChanged interface". /// /// public static readonly DiagnosticDescriptor DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError = new DiagnosticDescriptor( id: "MVVMTK0002", title: $"Duplicate {nameof(INotifyPropertyChanged)} definition", - messageFormat: $"Cannot apply [ObservableObject] to type {{0}}, as it already declares the {nameof(INotifyPropertyChanged)} interface", + messageFormat: $"Cannot apply [ObservableObject] to type {{0}}, as it already implements the {nameof(INotifyPropertyChanged)} interface", category: typeof(ObservableObjectGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, - description: $"Cannot apply [ObservableObject] to a type that already declares the {nameof(INotifyPropertyChanged)} interface.", + description: $"Cannot apply [ObservableObject] to a type that already implements the {nameof(INotifyPropertyChanged)} interface.", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0002"); /// - /// Gets a indicating when a duplicate declaration of would happen. + /// Gets a indicating when a duplicate implementation of would happen. /// - /// Format: "Cannot apply [ObservableObjectAttribute] to type {0}, as it already declares the INotifyPropertyChanging interface". + /// Format: "Cannot apply [ObservableObjectAttribute] to type {0}, as it already implements the INotifyPropertyChanging interface". /// /// public static readonly DiagnosticDescriptor DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError = new DiagnosticDescriptor( id: "MVVMTK0003", title: $"Duplicate {nameof(INotifyPropertyChanging)} definition", - messageFormat: $"Cannot apply [ObservableObject] to type {{0}}, as it already declares the {nameof(INotifyPropertyChanging)} interface", + messageFormat: $"Cannot apply [ObservableObject] to type {{0}}, as it already implements the {nameof(INotifyPropertyChanging)} interface", category: typeof(ObservableObjectGenerator).FullName, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, - description: $"Cannot apply [ObservableObject] to a type that already declares the {nameof(INotifyPropertyChanging)} interface.", + description: $"Cannot apply [ObservableObject] to a type that already implements the {nameof(INotifyPropertyChanging)} interface.", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0003"); /// diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 6e976501..084b0578 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -2,10 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Linq; using CommunityToolkit.Mvvm.SourceGenerators.Helpers; using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Linq; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; @@ -249,4 +250,23 @@ static void BuildFrom(ISymbol? symbol, in ImmutableArrayBuilder builder) BuildFrom(symbol, in builder); } + + /// + /// Checks whether a given implements at least one members of the specified interface. + /// + /// The input instance. + /// The fully qualified name of the interface. + /// Whether at least a single interface member is implemented. + public static bool ImplementsInterfaceMember(this ITypeSymbol symbol, string fullyQualifiedName) + { + INamedTypeSymbol? interfaceSymbol = symbol.AllInterfaces.FirstOrDefault(x => x.HasFullyQualifiedMetadataName(fullyQualifiedName)); + if (interfaceSymbol == null) + { + return false; + } + + IEnumerable interfaceMembers = interfaceSymbol.GetAllMembers(); + + return interfaceMembers.Any(x => symbol.FindImplementationForInterfaceMember(x) != null); + } } diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index fc767e71..fc476b62 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -39,6 +39,22 @@ public partial class SampleViewModel : INotifyPropertyChanged """; VerifyGeneratedDiagnostics(source, "MVVMTK0001"); + + string source_NoImplementation = """ + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [INotifyPropertyChanged] + public partial class SampleViewModel : INotifyPropertyChanged + { + + } + } + """; + + VerifyGeneratedDiagnostics(source_NoImplementation); } [TestMethod] @@ -87,6 +103,22 @@ public partial class SampleViewModel : INotifyPropertyChanged """; VerifyGeneratedDiagnostics(source, "MVVMTK0002"); + + string source_NoImplementation = """ + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [ObservableObject] + public partial class SampleViewModel : INotifyPropertyChanged + { + + } + } + """; + + VerifyGeneratedDiagnostics(source_NoImplementation); } [TestMethod] @@ -134,6 +166,22 @@ public partial class SampleViewModel : INotifyPropertyChanging """; VerifyGeneratedDiagnostics(source, "MVVMTK0003"); + + string source_NoImplementation = """ + using System.ComponentModel; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + [ObservableObject] + public partial class SampleViewModel : INotifyPropertyChanging + { + + } + } + """; + + VerifyGeneratedDiagnostics(source_NoImplementation); } [TestMethod]