diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs index 7e2aa7e57..0c4861ec4 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer.cs @@ -60,25 +60,35 @@ public override void Initialize(AnalysisContext context) return; } - // Warn on all [ObservableProperty] fields + // Warn on all [ObservableProperty] fields that would be included foreach (IFieldSymbol fieldSymbol in FindObservablePropertyFields(typeSymbol, observablePropertySymbol)) { - context.ReportDiagnostic(Diagnostic.Create( - WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField, - typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), - typeSymbol, - fieldSymbol.ContainingType, - fieldSymbol.Name)); + string propertyName = ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol); + + if (WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.DoesGeneratedBindableCustomPropertyAttributeIncludePropertyName(generatedBindableCustomPropertyAttribute, propertyName)) + { + context.ReportDiagnostic(Diagnostic.Create( + WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField, + typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), + typeSymbol, + fieldSymbol.ContainingType, + fieldSymbol.Name)); + } } - // Warn on all [RelayCommand] methods + // Warn on all [RelayCommand] methods that would be included foreach (IMethodSymbol methodSymbol in FindRelayCommandMethods(typeSymbol, relayCommandSymbol)) { - context.ReportDiagnostic(Diagnostic.Create( - WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand, - typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), - typeSymbol, - methodSymbol)); + (_, string propertyName) = RelayCommandGenerator.Execute.GetGeneratedFieldAndPropertyNames(methodSymbol); + + if (WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.DoesGeneratedBindableCustomPropertyAttributeIncludePropertyName(generatedBindableCustomPropertyAttribute, propertyName)) + { + context.ReportDiagnostic(Diagnostic.Create( + WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand, + typeSymbol.GetLocationFromAttributeDataOrDefault(generatedBindableCustomPropertyAttribute), + typeSymbol, + methodSymbol)); + } } }, SymbolKind.NamedType); }); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs index 7cbcf136a..10029b84b 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer.cs @@ -54,9 +54,17 @@ public override void Initialize(AnalysisContext context) return; } - // If the containing type is using [GeneratedBindableCustomProperty], emit a warning - if (typeSymbol.HasAttributeWithType(generatedBindableCustomPropertySymbol)) + // If the containing type is not using [GeneratedBindableCustomProperty], we can also skip it + if (!typeSymbol.TryGetAttributeWithType(generatedBindableCustomPropertySymbol, out AttributeData? generatedBindableCustomPropertyAttribute)) { + return; + } + + (_, string propertyName) = RelayCommandGenerator.Execute.GetGeneratedFieldAndPropertyNames(methodSymbol); + + if (DoesGeneratedBindableCustomPropertyAttributeIncludePropertyName(generatedBindableCustomPropertyAttribute, propertyName)) + { + // Actually warn if the generated command would've been included by the generator context.ReportDiagnostic(Diagnostic.Create( WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible, methodSymbol.GetLocationFromAttributeDataOrDefault(relayCommandAttribute), @@ -65,4 +73,32 @@ public override void Initialize(AnalysisContext context) }, SymbolKind.Method); }); } + + /// + /// Checks whether a generated property with a given name would be included by the [GeneratedBindableCustomProperty] generator. + /// + /// The input value for the [GeneratedBindableCustomProperty] attribute. + /// The target generated property name to check. + /// Whether would be included by the [GeneratedBindableCustomProperty] generator. + internal static bool DoesGeneratedBindableCustomPropertyAttributeIncludePropertyName(AttributeData attributeData, string propertyName) + { + // Make sure we have a valid list of property names to explicitly include. + // If that is not the case, we consider all properties as included by default. + if (attributeData.ConstructorArguments is not [{ IsNull: false, Kind: TypedConstantKind.Array, Type: IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_String }, Values: var names }, ..]) + { + return true; + } + + // Simply match the input collection of target property names + foreach (TypedConstant propertyValue in names) + { + if (propertyValue is { IsNull: false, Type.SpecialType: SpecialType.System_String, Value: string targetName } && targetName == propertyName) + { + return true; + } + } + + // No matches, we can consider the property as not included + return false; + } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 2de37f901..5196d5d89 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -766,49 +766,49 @@ internal static class DiagnosticDescriptors /// /// Gets a for when [RelayCommand] is used on a method in types where [GeneratedBindableCustomProperty] is used. /// - /// Format: "The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty], which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)". + /// Format: "The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty] and including the generated property, which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)". /// /// public static readonly DiagnosticDescriptor WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatible = new DiagnosticDescriptor( id: "MVVMTK0046", title: "Using [RelayCommand] is not compatible with [GeneratedBindableCustomProperty]", - messageFormat: """The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty], which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)""", + messageFormat: """The method {0} using [RelayCommand] within a type also using [GeneratedBindableCustomProperty] and including the generated property, which is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator)""", category: typeof(RelayCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Using [RelayCommand] on methods within a type also using [GeneratedBindableCustomProperty] is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator).", + description: "Using [RelayCommand] on methods within a type also using [GeneratedBindableCustomProperty] and including the generated property is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated command property that is produced by the MVVM Toolkit generator).", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0046"); /// /// Gets a for when [GeneratedBindableCustomProperty] is used on a type that also uses [ObservableProperty] on any declared or inherited fields. /// - /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)". + /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}, and including the generated property: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)". /// /// public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseObservablePropertyOnField = new DiagnosticDescriptor( id: "MVVMTK0047", title: "Using [GeneratedBindableCustomProperty] is not compatible with [ObservableProperty] on fields", - messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""", + messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [ObservableProperty] on its declared (or inherited) field {1}.{2}, and including the generated property: combining the two generators is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""", category: typeof(ObservablePropertyGenerator).FullName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Using [GeneratedBindableCustomProperty] on types that also use [ObservableProperty] on any declared (or inherited) fields is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).", + description: "Using [GeneratedBindableCustomProperty] on types that also use [ObservableProperty] on any declared (or inherited) fields and including the generated property is not supported, and partial properties should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0047"); /// /// Gets a for when [GeneratedBindableCustomProperty] is used on a type that also uses [RelayCommand] on any declared or inherited methods. /// - /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)". + /// Format: "The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1} and including the generated property: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)". /// /// public static readonly DiagnosticDescriptor WinRTGeneratedBindableCustomPropertyWithBaseRelayCommand = new DiagnosticDescriptor( id: "MVVMTK0048", title: "Using [GeneratedBindableCustomProperty] is not compatible with [RelayCommand]", - messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1}: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""", + messageFormat: """The type {0} using [GeneratedBindableCustomProperty] is also using [RelayCommand] on its inherited method {1} and including the generated property: combining the two generators is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator)""", category: typeof(RelayCommandGenerator).FullName, defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Using [GeneratedBindableCustomProperty] on types that also use [RelayCommand] on any inherited methods is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).", + description: "Using [GeneratedBindableCustomProperty] on types that also use [RelayCommand] on any inherited methods and including the generated property is not supported, and a manually declared command property should be used instead (the [GeneratedBindableCustomProperty] generator cannot see the generated property that is produced by the MVVM Toolkit generator).", helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0048"); /// diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs index 91c35db10..07ac71412 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -747,6 +747,119 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + + [TestMethod] + public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Field_NotIncluded_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + using WinRT; + + namespace MyApp + { + [GeneratedBindableCustomProperty(["OtherName"], [])] + public partial class SampleViewModel : BaseViewModel + { + } + + public partial class BaseViewModel : ObservableObject + { + [ObservableProperty] + private string name; + } + } + + namespace WinRT + { + public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute + { + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + + [TestMethod] + public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Method_NotIncluded_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; + using WinRT; + + namespace MyApp + { + [GeneratedBindableCustomProperty(["OtherMethod"], [])] + public partial class SampleViewModel : BaseViewModel + { + } + + public partial class BaseViewModel : ObservableObject + { + [RelayCommand] + private void DoStuff() + { + } + } + } + + namespace WinRT + { + public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute + { + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + [TestMethod] public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Field_Warns() { @@ -781,6 +894,42 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + [TestMethod] public async Task WinRTGeneratedBindableCustomPropertyWithBasesMemberAnalyzer_TargetingWindows_BaseType_Method_Warns() { @@ -818,6 +967,45 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + [TestMethod] public async Task InvalidPartialPropertyLevelObservablePropertyAttributeAnalyzer_OnValidProperty_DoesNotWarn() { diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index 8030231aa..cb866377d 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -2049,6 +2049,111 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + + [TestMethod] + public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_NotIncluded2_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; + using WinRT; + + namespace MyApp + { + [GeneratedBindableCustomProperty(["OtherCommand"], [])] + public partial class SampleViewModel : ObservableObject + { + [RelayCommand] + private void DoStuff() + { + } + } + } + + namespace WinRT + { + public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute + { + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + + [TestMethod] + public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_NotIncluded3_DoesNotWarn() + { + const string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; + using WinRT; + + namespace MyApp + { + [GeneratedBindableCustomProperty(["OtherCommand"], ["DoStuffCommand"])] + public partial class SampleViewModel : ObservableObject + { + [RelayCommand] + private void DoStuff() + { + } + } + } + + namespace WinRT + { + public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute + { + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + [TestMethod] public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Warns() { @@ -2084,6 +2189,76 @@ await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + + [TestMethod] + public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Included2_Warns() + { + const string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + using CommunityToolkit.Mvvm.Input; + using WinRT; + + namespace MyApp + { + [GeneratedBindableCustomProperty(["Blah", "", "DoStuffCommand"], [])] + public partial class SampleViewModel : ObservableObject + { + [RelayCommand] + private void {|MVVMTK0046:DoStuff|}() + { + } + } + } + + namespace WinRT + { + public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute + { + } + } + """; + + await CSharpAnalyzerWithLanguageVersionTest.VerifyAnalyzerAsync( + source, + LanguageVersion.CSharp12, + editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]); + } + [TestMethod] public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_NotTargetingWindows_DoesNotWarn() {