diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs index 2bf62d0de..1f23747ca 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/PropertyInfo.cs @@ -20,6 +20,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models; /// Whether the old property value is being directly referenced. /// Indicates whether the property is of a reference type or an unconstrained type parameter. /// Indicates whether to include nullability annotations on the setter. +/// Indicates whether the generated property should hide an inherited property declaration. /// The sequence of forwarded attributes for the generated property. internal sealed record PropertyInfo( string TypeNameWithNullabilityAnnotations, @@ -33,4 +34,5 @@ internal sealed record PropertyInfo( bool IsOldPropertyValueDirectlyReferenced, bool IsReferenceTypeOrUnconstraindTypeParameter, bool IncludeMemberNotNullOnSetAccessor, + bool hidesInheritedProperty, EquatableArray ForwardedAttributes); diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 855dce1fb..8aab9e152 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel; @@ -118,6 +119,7 @@ public static bool TryGetInfo( bool hasOrInheritsClassLevelNotifyPropertyChangedRecipients = false; bool hasOrInheritsClassLevelNotifyDataErrorInfo = false; bool hasAnyValidationAttributes = false; + bool hidesInheritedProperty = false; bool isOldPropertyValueDirectlyReferenced = IsOldPropertyValueDirectlyReferenced(fieldSymbol, propertyName); token.ThrowIfCancellationRequested(); @@ -194,6 +196,15 @@ public static bool TryGetInfo( forwardedAttributes.Add(AttributeInfo.Create(attributeData)); } + // Check if the generated property should hide an inherited property declaration + if (attributeData.AttributeClass?.HasFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true) + { + if (Convert.ToBoolean(attributeData.ConstructorArguments[0].Value) == true) + { + hidesInheritedProperty = true; + } + } + // Also track the current attribute for forwarding if it is of any of the following types: // - Display attributes (System.ComponentModel.DataAnnotations.DisplayAttribute) // - UI hint attributes(System.ComponentModel.DataAnnotations.UIHintAttribute) @@ -308,6 +319,7 @@ public static bool TryGetInfo( isOldPropertyValueDirectlyReferenced, isReferenceTypeOrUnconstraindTypeParameter, includeMemberNotNullOnSetAccessor, + hidesInheritedProperty, forwardedAttributes.ToImmutable()); diagnostics = builder.ToImmutable(); @@ -1016,11 +1028,18 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // - // public + // public // { // get => ; // // } + + List propertyDeclarationModifier = new() { Token(SyntaxKind.PublicKeyword) }; + if (propertyInfo.hidesInheritedProperty) + { + propertyDeclarationModifier.Add(Token(SyntaxKind.NewKeyword)); + } + return PropertyDeclaration(propertyType, Identifier(propertyInfo.PropertyName)) .AddAttributeLists( @@ -1032,7 +1051,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf .WithOpenBracketToken(Token(TriviaList(Comment($"/// ")), SyntaxKind.OpenBracketToken, TriviaList())), AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))) .AddAttributeLists(forwardedAttributes.ToArray()) - .AddModifiers(Token(SyntaxKind.PublicKeyword)) + .AddModifiers(propertyDeclarationModifier.ToArray()) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithExpressionBody(ArrowExpressionClause(getterFieldExpression)) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs index 0d03495d5..c6373961f 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Models/HierarchyInfo.Syntax.cs @@ -60,10 +60,17 @@ public CompilationUnitSyntax GetCompilationUnit( // // // #pragma warning disable + // #pragma warning restore CS0108 // Member hides inherited member; missing new keyword + // // #nullable enable + + SeparatedSyntaxList pragmaWarningCodesToPreserve = new SeparatedSyntaxList() + .Add(IdentifierName(Identifier(default, SyntaxKind.IdentifierName, "CS0108", "CS0108", TriviaList(Comment("// Member hides inherited member; missing new keyword"))))); + SyntaxTriviaList syntaxTriviaList = TriviaList( Comment("// "), Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)), + Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.RestoreKeyword), pragmaWarningCodesToPreserve, true)), Trivia(NullableDirectiveTrivia(Token(SyntaxKind.EnableKeyword), true))); if (Namespace is "") diff --git a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs index 0e765267a..a1e22b122 100644 --- a/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs +++ b/src/CommunityToolkit.Mvvm/ComponentModel/Attributes/ObservablePropertyAttribute.cs @@ -54,4 +54,19 @@ namespace CommunityToolkit.Mvvm.ComponentModel; [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] public sealed class ObservablePropertyAttribute : Attribute { + /// + /// Initializes a new instance of the class. + /// + /// + /// Specifies if the generated property will hide an inherited property declaration. + /// + public ObservablePropertyAttribute(bool hidesInheritedProperty = false) + { + Hidesinheritedproperty = hidesInheritedProperty; + } + + /// + /// Specifies if the generated property will hide an inherited property declaration. + /// + public bool Hidesinheritedproperty { get; } }