From 9c1feacc9299a547e63bf676784cf701549d6953 Mon Sep 17 00:00:00 2001 From: Morgan Le Fay <32407205+neominky@users.noreply.github.com> Date: Sat, 9 Nov 2024 09:14:35 +0900 Subject: [PATCH] Feature overridable Inheritance property (#123) * support overridable property * Update to use enum, fix tests --------- Co-authored-by: Chris Pulman --- ...rceGenerators.AccessModifier.g.verified.cs | 11 +++++ ...Generators.ReactiveAttribute.g.verified.cs | 5 +++ ...rceGenerators.AccessModifier.g.verified.cs | 11 +++++ ...Generators.ReactiveAttribute.g.verified.cs | 5 +++ ...rceGenerators.AccessModifier.g.verified.cs | 11 +++++ ...Generators.ReactiveAttribute.g.verified.cs | 5 +++ ...rceGenerators.AccessModifier.g.verified.cs | 36 +++++++++++++++++ ...Generators.ReactiveAttribute.g.verified.cs | 35 ++++++++++++++++ ...nheritance#TestVM.Properties.g.verified.cs | 23 +++++++++++ .../UnitTests/ReactiveGeneratorTests.cs | 34 ++++++++++++++++ .../TestViewModel.cs | 5 +++ .../AttributeDefinitions.cs | 22 ++++++++++ ...eAsPropertyGenerator{FromField}.Execute.cs | 13 +++++- .../Reactive/Models/PropertyInfo.cs | 3 +- .../Reactive/ReactiveGenerator.Execute.cs | 40 ++++++++++--------- 15 files changed, 239 insertions(+), 20 deletions(-) create mode 100644 src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs create mode 100644 src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs create mode 100644 src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestVM.Properties.g.verified.cs diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index b30d4d5..1756eef 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -21,5 +21,16 @@ internal enum AccessModifier InternalProtected, PrivateProtected, } + +/// +/// InheritanceModifier. +/// +internal enum InheritanceModifier +{ + None, + Virtual, + Override, + New, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs index bd89c3c..3fde86e 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs @@ -25,6 +25,11 @@ internal sealed class ReactiveAttribute : Attribute /// The AccessModifier of the set property. /// public AccessModifier SetModifier { get; init; } + + /// + /// Gets the InheritanceModifier of the property. + /// + public InheritanceModifier Inheritance { get; init; } } #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index b30d4d5..1756eef 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -21,5 +21,16 @@ internal enum AccessModifier InternalProtected, PrivateProtected, } + +/// +/// InheritanceModifier. +/// +internal enum InheritanceModifier +{ + None, + Virtual, + Override, + New, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs index bd89c3c..3fde86e 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs @@ -25,6 +25,11 @@ internal sealed class ReactiveAttribute : Attribute /// The AccessModifier of the set property. /// public AccessModifier SetModifier { get; init; } + + /// + /// Gets the InheritanceModifier of the property. + /// + public InheritanceModifier Inheritance { get; init; } } #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index b30d4d5..1756eef 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -21,5 +21,16 @@ internal enum AccessModifier InternalProtected, PrivateProtected, } + +/// +/// InheritanceModifier. +/// +internal enum InheritanceModifier +{ + None, + Virtual, + Override, + New, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs index bd89c3c..3fde86e 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs @@ -25,6 +25,11 @@ internal sealed class ReactiveAttribute : Attribute /// The AccessModifier of the set property. /// public AccessModifier SetModifier { get; init; } + + /// + /// Gets the InheritanceModifier of the property. + /// + public InheritanceModifier Inheritance { get; init; } } #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs new file mode 100644 index 0000000..1756eef --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -0,0 +1,36 @@ +//HintName: ReactiveUI.SourceGenerators.AccessModifier.g.cs +// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// +#pragma warning disable +#nullable enable +namespace ReactiveUI.SourceGenerators; + +/// +/// AccessModifier. +/// +internal enum AccessModifier +{ + Public, + Protected, + Internal, + Private, + InternalProtected, + PrivateProtected, +} + +/// +/// InheritanceModifier. +/// +internal enum InheritanceModifier +{ + None, + Virtual, + Override, + New, +} +#nullable restore +#pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs new file mode 100644 index 0000000..3fde86e --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs @@ -0,0 +1,35 @@ +//HintName: ReactiveUI.SourceGenerators.ReactiveAttribute.g.cs +// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; + +// +#pragma warning disable +#nullable enable +namespace ReactiveUI.SourceGenerators; + +/// +/// ReactiveAttribute. +/// +/// +[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] +internal sealed class ReactiveAttribute : Attribute +{ + /// + /// Gets the AccessModifier of the set property. + /// + /// + /// The AccessModifier of the set property. + /// + public AccessModifier SetModifier { get; init; } + + /// + /// Gets the InheritanceModifier of the property. + /// + public InheritanceModifier Inheritance { get; init; } +} +#nullable restore +#pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestVM.Properties.g.verified.cs new file mode 100644 index 0000000..265a551 --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestVM.Properties.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: TestVM.Properties.g.cs +// +using ReactiveUI; + +#pragma warning disable +#nullable enable + +namespace TestNs +{ + /// + /// Partial class for the TestVM which contains ReactiveUI Reactive property initialization. + /// + public partial class TestVM + { + /// + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + [global::System.Runtime.Serialization.DataMemberAttribute()] + [global::System.Text.Json.Serialization.JsonIncludeAttribute()] + public string? Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); } + } +} +#nullable restore +#pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs index 454a53d..3b7899c 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs @@ -109,5 +109,39 @@ public partial class TestVM : ReactiveObject return VerifyGenerator(driver); } + /// + /// Froms the reactive properties with attributes and access and inheritance. + /// + /// A task to monitor the async. + [Fact] + public Task FromReactivePropertiesWithAttributesAccessAndInheritance() + { + // Arrange: Setup the source code that matches the generator input expectations. + const string sourceCode = @" + using System; + using System.Runtime.Serialization; + using System.Text.Json.Serialization; + using ReactiveUI; + using ReactiveUI.SourceGenerators; + using System.Reactive.Linq; + + namespace TestNs; + + public partial class TestVM : ReactiveObject + { + [property: JsonInclude] + [DataMember] + [Reactive(Inheritance = InheritanceModifier.Virtual, SetModifier = AccessModifier.Protected)] + private string? _name; + } + "; + + // Act: Initialize the helper and run the generator. + var driver = TestHelper.TestPass(sourceCode); + + // Assert: Verify the generated code. + return VerifyGenerator(driver); + } + private SettingsTask VerifyGenerator(GeneratorDriver driver) => Verify(driver).UseDirectory(TestHelper.VerifiedFilePath()).ScrubLinesContaining("[global::System.CodeDom.Compiler.GeneratedCode(\""); } diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index 37b31aa..eb453ea 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -51,6 +51,11 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis [Reactive] private string _myStringProperty = "test"; + [property: JsonInclude] + [DataMember] + [Reactive(Inheritance = InheritanceModifier.Virtual, SetModifier = AccessModifier.Protected)] + private string? _name; + /// /// Initializes a new instance of the class. /// diff --git a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs index 418bd0c..5baba36 100644 --- a/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs +++ b/src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs @@ -40,6 +40,17 @@ internal enum AccessModifier InternalProtected, PrivateProtected, } + + /// + /// InheritanceModifier. + /// + internal enum InheritanceModifier + { + None, + Virtual, + Override, + New, + } #nullable restore #pragma warning restore """; @@ -105,6 +116,12 @@ internal sealed class ReactiveCommandAttribute : Attribute public const string ReactiveAttributeType = "ReactiveUI.SourceGenerators.ReactiveAttribute"; + /// + /// Gets the reactive attribute. + /// + /// + /// The reactive attribute. + /// public static string ReactiveAttribute => $$""" // Copyright (c) {{DateTime.Now.Year}} .NET Foundation and Contributors. All rights reserved. // Licensed to the .NET Foundation under one or more agreements. @@ -133,6 +150,11 @@ internal sealed class ReactiveAttribute : Attribute /// The AccessModifier of the set property. /// public AccessModifier SetModifier { get; init; } + + /// + /// Gets the InheritanceModifier of the property. + /// + public InheritanceModifier Inheritance { get; init; } } #nullable restore #pragma warning restore diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs index b4aaa58..f49f68b 100644 --- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.Execute.cs @@ -55,6 +55,16 @@ public sealed partial class ObservableAsPropertyGenerator // Get the can PropertyName member, if any attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly); + // Get Inheritance value from the attribute + attributeData.TryGetNamedArgument("Inheritance", out int? inheritanceArgument); + var inheritance = inheritanceArgument switch + { + 1 => " virtual", + 2 => " override", + 3 => " new", + _ => string.Empty, + }; + token.ThrowIfCancellationRequested(); // Get the property type and name @@ -198,7 +208,8 @@ public sealed partial class ObservableAsPropertyGenerator isReferenceTypeOrUnconstraindTypeParameter, includeMemberNotNullOnSetAccessor, forwardedPropertyAttributes, - isReadonly == false ? string.Empty : "readonly"), + isReadonly == false ? string.Empty : "readonly", + inheritance), builder.ToImmutable()); } diff --git a/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs b/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs index 140be3b..bcb7c1a 100644 --- a/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs +++ b/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs @@ -24,4 +24,5 @@ internal sealed record PropertyInfo( bool IsReferenceTypeOrUnconstrainedTypeParameter, bool IncludeMemberNotNullOnSetAccessor, EquatableArray ForwardedAttributes, - string AccessModifier); + string AccessModifier, + string Inheritance); diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs index cc95503..169228d 100644 --- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs @@ -63,16 +63,25 @@ public sealed partial class ReactiveGenerator token.ThrowIfCancellationRequested(); // Get AccessModifier enum value from the attribute - attributeData.TryGetNamedArgument("SetModifier", out int? accessModifierArgument); + attributeData.TryGetNamedArgument("SetModifier", out int accessModifierArgument); var accessModifier = accessModifierArgument switch { - 0 => "public", - 1 => "protected", - 2 => "internal", - 3 => "private", - 4 => "internal protected", - 5 => "private protected", - _ => "public", + 1 => "protected ", + 2 => "internal ", + 3 => "private ", + 4 => "internal protected ", + 5 => "private protected ", + _ => string.Empty, + }; + + // Get Inheritance value from the attribute + attributeData.TryGetNamedArgument("Inheritance", out int inheritanceArgument); + var inheritance = inheritanceArgument switch + { + 1 => " virtual", + 2 => " override", + 3 => " new", + _ => string.Empty, }; token.ThrowIfCancellationRequested(); @@ -214,7 +223,8 @@ public sealed partial class ReactiveGenerator isReferenceTypeOrUnconstraindTypeParameter, includeMemberNotNullOnSetAccessor, forwardedAttributesString, - accessModifier), + accessModifier, + inheritance), builder.ToImmutable()); } @@ -268,12 +278,6 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) return string.Empty; } - var setModifier = propertyInfo.AccessModifier + " "; - if (setModifier == "public ") - { - setModifier = string.Empty; - } - var propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes)); if (propertyInfo.IncludeMemberNotNullOnSetAccessor) @@ -282,11 +286,11 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) $$""" /// {{propertyAttributes}} - {{propertyInfo.TargetVisibility}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} + {{propertyInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; [global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{propertyInfo.FieldName}}")] - {{setModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); + {{propertyInfo.AccessModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); } """; } @@ -295,7 +299,7 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) $$""" /// {{propertyAttributes}} - {{propertyInfo.TargetVisibility}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{setModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); } + {{propertyInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); } """; }