From f742c97d3aad8867932b2b9de2dc8f2993210085 Mon Sep 17 00:00:00 2001 From: Trigve Siver Date: Wed, 21 Aug 2024 21:41:23 +0200 Subject: [PATCH] Support for custom name for generated property in ObservablePropertyAttribute --- .../ObservablePropertyGenerator.Execute.cs | 5 +++- .../ObservablePropertyGenerator.cs | 4 +++- .../Attributes/ObservablePropertyAttribute.cs | 17 +++++++++++++- .../Test_ObservablePropertyAttribute.cs | 23 +++++++++++++++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index c6bf051a4..1e04959be 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -38,12 +38,14 @@ internal static class Execute /// The cancellation token for the current operation. /// The resulting value, if successfully retrieved. /// The resulting diagnostics from the processing operation. + /// Generated property name. If empty, the property name will be generated from the field name. /// The resulting instance for , if successful. public static bool TryGetInfo( FieldDeclarationSyntax fieldSyntax, IFieldSymbol fieldSymbol, SemanticModel semanticModel, CancellationToken token, + string PropertyName, [NotNullWhen(true)] out PropertyInfo? propertyInfo, out ImmutableArray diagnostics) { @@ -69,7 +71,8 @@ public static bool TryGetInfo( // Get the property type and name string typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(); string fieldName = fieldSymbol.Name; - string propertyName = GetGeneratedPropertyName(fieldSymbol); + // Generate if not specified + string propertyName = string.IsNullOrEmpty(PropertyName) ? GetGeneratedPropertyName(fieldSymbol) : PropertyName; // Check for name collisions if (fieldName == propertyName) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs index ad7e5fe03..749aec6fd 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs @@ -44,7 +44,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); - _ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out PropertyInfo? propertyInfo, out ImmutableArray diagnostics); + string propertyName = context.Attributes.FirstOrDefault()?.GetConstructorArguments().FirstOrDefault() ?? string.Empty;; + + _ = Execute.TryGetInfo(fieldDeclaration, fieldSymbol, context.SemanticModel, token, propertyName, out PropertyInfo? propertyInfo, out ImmutableArray diagnostics); token.ThrowIfCancellationRequested(); diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs index 0e765267a..8a91b1525 100644 --- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs +++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs @@ -49,9 +49,24 @@ namespace CommunityToolkit.Mvvm.ComponentModel; /// The generated properties will automatically use the UpperCamelCase format for their names, /// which will be derived from the field names. The generator can also recognize fields using either /// the _lowerCamel or m_lowerCamel naming scheme. Otherwise, the first character in the -/// source field name will be converted to uppercase (eg. isEnabled to IsEnabled). +/// source field name will be converted to uppercase (eg. isEnabled to IsEnabled). For +/// custom property names use the /// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class ObservablePropertyAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + public ObservablePropertyAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Generated property name. + public ObservablePropertyAttribute(string name) + { + } } diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs index 495a9cd59..d1b0dc9fa 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs @@ -1101,6 +1101,22 @@ public void Test_ObservableProperty_ForwardedAttributesWithNegativeValues() .Value); } + // See https://github.com/CommunityToolkit/dotnet/issues/795 + [TestMethod] + public void Test_ObservableProperty_ModelWithPropertyNames() + { + ModelWithPropertyNames model = new(); + + List propertyNames = new(); + + model.PropertyChanged += (s, e) => propertyNames.Add(e.PropertyName); + + CollectionAssert.AreEqual(new[] + { + nameof(model.fieldProp), + }, propertyNames); + } + public abstract partial class BaseViewModel : ObservableObject { public string? Content { get; set; } @@ -1797,4 +1813,11 @@ private sealed partial class ModelWithDependentPropertyAndNoPropertyChanging public string? FullName => ""; } + + public partial class ModelWithPropertyNames : ObservableObject + { + // Inherited property + [ObservableProperty("fieldProp")] + private string? m_field; + } }