mirror of
https://github.com/chylex/.NET-Community-Toolkit.git
synced 2025-02-24 15:46:02 +01:00
Add diagnostics for invalid [ObservableProperty] containing type
This commit is contained in:
parent
914a7aca26
commit
b632429c15
CommunityToolkit.Mvvm.SourceGenerators
CommunityToolkit.Mvvm/ComponentModel/Attributes
tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests
@ -23,3 +23,4 @@ ### New Rules
|
||||
MVVMTK0016 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
|
||||
MVVMTK0017 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
|
||||
MVVMTK0018 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
|
||||
MVVMTK0019 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
|
||||
|
@ -34,7 +34,7 @@ public INotifyPropertyChangedGenerator()
|
||||
{
|
||||
static INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
|
||||
{
|
||||
bool includeAdditionalHelperMethods = attributeData.GetNamedArgument<bool>("IncludeAdditionalHelperMethods", true);
|
||||
bool includeAdditionalHelperMethods = attributeData.GetNamedArgument("IncludeAdditionalHelperMethods", true);
|
||||
|
||||
return new(includeAdditionalHelperMethods);
|
||||
}
|
||||
|
@ -34,11 +34,19 @@ internal static class Execute
|
||||
{
|
||||
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();
|
||||
|
||||
// Check whether the containing type implements INotifyPropertyChanging and whether it inherits from ObservableValidator
|
||||
bool isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObject");
|
||||
bool isObservableValidator = fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
|
||||
bool isNotifyPropertyChanging = fieldSymbol.ContainingType.AllInterfaces.Any(static i => i.HasFullyQualifiedName("global::System.ComponentModel.INotifyPropertyChanging"));
|
||||
bool hasObservableObjectAttribute = fieldSymbol.ContainingType.GetAttributes().Any(static a => a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") == true);
|
||||
// Validate the target type
|
||||
if (!IsTargetTypeValid(fieldSymbol, out bool shouldInvokeOnPropertyChanging))
|
||||
{
|
||||
builder.Add(
|
||||
InvalidContainingTypeForObservablePropertyFieldError,
|
||||
fieldSymbol,
|
||||
fieldSymbol.ContainingType,
|
||||
fieldSymbol.Name);
|
||||
|
||||
diagnostics = builder.ToImmutable();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the property type and name
|
||||
string typeName = fieldSymbol.Type.GetFullyQualifiedName();
|
||||
@ -69,7 +77,7 @@ internal static class Execute
|
||||
ImmutableArray<AttributeInfo>.Builder validationAttributes = ImmutableArray.CreateBuilder<AttributeInfo>();
|
||||
|
||||
// Track the property changing event for the property, if the type supports it
|
||||
if (isObservableObject || isNotifyPropertyChanging || hasObservableObjectAttribute)
|
||||
if (shouldInvokeOnPropertyChanging)
|
||||
{
|
||||
propertyChangingNames.Add(propertyName);
|
||||
}
|
||||
@ -96,7 +104,7 @@ internal static class Execute
|
||||
|
||||
// Log the diagnostics if needed
|
||||
if (validationAttributes.Count > 0 &&
|
||||
!isObservableValidator)
|
||||
!fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"))
|
||||
{
|
||||
builder.Add(
|
||||
MissingObservableValidatorInheritanceError,
|
||||
@ -124,6 +132,30 @@ internal static class Execute
|
||||
validationAttributes.ToImmutable());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the containing type for a given field being annotated.
|
||||
/// </summary>
|
||||
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
|
||||
/// <param name="shouldInvokeOnPropertyChanging">Whether or not property changing events should also be raised.</param>
|
||||
/// <returns>Whether or not the containing type for <paramref name="fieldSymbol"/> is valid.</returns>
|
||||
private static bool IsTargetTypeValid(
|
||||
IFieldSymbol fieldSymbol,
|
||||
out bool shouldInvokeOnPropertyChanging)
|
||||
{
|
||||
// The [ObservableProperty] attribute can only be used in types that are known to expose the necessary OnPropertyChanged and OnPropertyChanging methods.
|
||||
// That means that the containing type for the field needs to match one of the following conditions:
|
||||
// - It inherits from ObservableObject (in which case it also implements INotifyPropertyChanging).
|
||||
// - It has the [ObservableObject] attribute (on itself or any of its base types).
|
||||
// - It has the [INotifyPropertyChanged] attribute (on itself or any of its base types).
|
||||
bool isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObject");
|
||||
bool hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute");
|
||||
bool hasINotifyPropertyChangedAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute");
|
||||
|
||||
shouldInvokeOnPropertyChanging = isObservableObject || hasObservableObjectAttribute;
|
||||
|
||||
return isObservableObject || hasObservableObjectAttribute || hasINotifyPropertyChangedAttribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to gather dependent properties from the given attribute.
|
||||
/// </summary>
|
||||
|
@ -233,7 +233,7 @@ internal static class DiagnosticDescriptors
|
||||
category: typeof(ObservablePropertyGenerator).FullName,
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: $"The name of fields annotated with [ObservableProperty] should use \"lowerCamel\", \"_lowerCamel\" or \"m_lowerCamel\" pattern to avoid collisions with the generated properties.",
|
||||
description: "The name of fields annotated with [ObservableProperty] should use \"lowerCamel\", \"_lowerCamel\" or \"m_lowerCamel\" pattern to avoid collisions with the generated properties.",
|
||||
helpLinkUri: "https://aka.ms/mvvmtoolkit");
|
||||
|
||||
/// <summary>
|
||||
@ -281,7 +281,7 @@ internal static class DiagnosticDescriptors
|
||||
category: typeof(INotifyPropertyChangedGenerator).FullName,
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: $"Cannot apply [INotifyPropertyChanged] to a type that already has this attribute or [ObservableObject] applied to it (including base types).",
|
||||
description: "Cannot apply [INotifyPropertyChanged] to a type that already has this attribute or [ObservableObject] applied to it (including base types).",
|
||||
helpLinkUri: "https://aka.ms/mvvmtoolkit");
|
||||
|
||||
/// <summary>
|
||||
@ -297,6 +297,22 @@ internal static class DiagnosticDescriptors
|
||||
category: typeof(ObservableObjectGenerator).FullName,
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: $"Cannot apply [ObservableObject] to a type that already has this attribute or [INotifyPropertyChanged] applied to it (including base types).",
|
||||
description: "Cannot apply [ObservableObject] to a type that already has this attribute or [INotifyPropertyChanged] applied to it (including base types).",
|
||||
helpLinkUri: "https://aka.ms/mvvmtoolkit");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[ObservableProperty]</c> is applied to a field in an invalid type.
|
||||
/// <para>
|
||||
/// Format: <c>"The field {0}.{1} cannot be used to generate an observable property, as its containing type doesn't inherit from ObservableObject, nor does it use [ObservableObject] or [INotifyPropertyChanged]"</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static readonly DiagnosticDescriptor InvalidContainingTypeForObservablePropertyFieldError = new DiagnosticDescriptor(
|
||||
id: "MVVMTK0019",
|
||||
title: $"Invalid containing type for [ObservableProperty] field",
|
||||
messageFormat: $"The field {{0}}.{{1}} cannot be used to generate an observable property, as its containing type doesn't inherit from ObservableObject, nor does it use [ObservableObject] or [INotifyPropertyChanged]",
|
||||
category: typeof(ObservablePropertyGenerator).FullName,
|
||||
defaultSeverity: DiagnosticSeverity.Error,
|
||||
isEnabledByDefault: true,
|
||||
description: "Fields annotated with [ObservableProperty] must be contained in a type that inherits from ObservableObject or that is annotated with [ObservableObject] or [INotifyPropertyChanged] (including base types).",
|
||||
helpLinkUri: "https://aka.ms/mvvmtoolkit");
|
||||
}
|
||||
|
@ -10,10 +10,11 @@ namespace CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute that indicates that a given field should be wrapped by a generated observable property.
|
||||
/// In order to use this attribute, the containing type has to implement the <see cref="INotifyPropertyChanged"/> interface
|
||||
/// and expose a method with the same signature as <see cref="ObservableObject.OnPropertyChanged(string?)"/>. If the containing
|
||||
/// type also implements the <see cref="INotifyPropertyChanging"/> interface and exposes a method with the same signature as
|
||||
/// <see cref="ObservableObject.OnPropertyChanging(string?)"/>, then this method will be invoked as well by the property setter.
|
||||
/// In order to use this attribute, the containing type has to inherit from <see cref="ObservableObject"/>, or it
|
||||
/// must be using <see cref="ObservableObjectAttribute"/> or <see cref="INotifyPropertyChangedAttribute"/>.
|
||||
/// If the containing type also implements the <see cref="INotifyPropertyChanging"/> (that is, if it either inherits from
|
||||
/// <see cref="ObservableObject"/> or is using <see cref="ObservableObjectAttribute"/>), then the generated code will
|
||||
/// also invoke <see cref="ObservableObject.OnPropertyChanging(PropertyChangingEventArgs)"/> to signal that event.
|
||||
/// <para>
|
||||
/// This attribute can be used as follows:
|
||||
/// <code>
|
||||
|
@ -620,7 +620,6 @@ private async Task GreetUserAsync(User user)
|
||||
public void NameCollisionForGeneratedObservableProperty()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -639,7 +638,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyChangeForInvalidTargetError_Null()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -659,7 +657,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyChangeForInvalidTargetError_SamePropertyAsGeneratedOneFromSelf()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -679,7 +676,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyChangeForInvalidTargetError_Missing()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -699,7 +695,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyChangeForInvalidTargetError_InvalidType()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -723,7 +718,6 @@ public void Foo()
|
||||
public void AlsoNotifyCanExecuteForInvalidTargetError_Null()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -743,7 +737,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyCanExecuteForInvalidTargetError_Missing()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -763,7 +756,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyCanExecuteForInvalidTargetError_InvalidMemberType()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -787,7 +779,6 @@ public void Foo()
|
||||
public void AlsoNotifyCanExecuteForInvalidTargetError_InvalidPropertyType()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
@ -809,7 +800,6 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void AlsoNotifyCanExecuteForInvalidTargetError_InvalidCommandType()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
@ -832,9 +822,7 @@ public partial class SampleViewModel : ObservableObject
|
||||
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingINotifyPropertyChangedAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -856,9 +844,7 @@ public partial class B : A
|
||||
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingObservableObjectAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -880,9 +866,7 @@ public partial class B : A
|
||||
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_WithAlsoObservableObjectAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -900,9 +884,7 @@ public partial class A
|
||||
public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingINotifyPropertyChangedAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -924,9 +906,7 @@ public partial class B : A
|
||||
public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingObservableObjectAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -948,9 +928,7 @@ public partial class B : A
|
||||
public void InvalidAttributeCombinationForObservableObjectAttributeError_WithAlsoINotifyPropertyChangedAttribute()
|
||||
{
|
||||
string source = @"
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
@ -964,6 +942,26 @@ public partial class A
|
||||
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0018");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InvalidContainingTypeForObservablePropertyFieldError()
|
||||
{
|
||||
string source = @"
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
public partial class MyViewModel : INotifyPropertyChanged
|
||||
{
|
||||
[ObservableProperty]
|
||||
public int number;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
}";
|
||||
|
||||
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0019");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the output of a source generator.
|
||||
/// </summary>
|
||||
|
Loading…
Reference in New Issue
Block a user