diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs new file mode 100644 index 0000000..2f7525d --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -0,0 +1,37 @@ +//HintName: ReactiveUI.SourceGenerators.AccessModifier.g.cs +// Copyright (c) 2025 .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, + Init, +} + +/// +/// 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.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs new file mode 100644 index 0000000..a13d64c --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.ReactiveAttribute.g.verified.cs @@ -0,0 +1,40 @@ +//HintName: ReactiveUI.SourceGenerators.ReactiveAttribute.g.cs +// Copyright (c) 2025 .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 | AttributeTargets.Property, 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; } + + /// + /// Use Required attribute to indicate that the property is required. + /// + public bool UseRequired { get; init; } +} +#nullable restore +#pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs new file mode 100644 index 0000000..c8b969d --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs @@ -0,0 +1,27 @@ +//HintName: TestNs.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] + public string Value + { + get => value; + [global::System.Diagnostics.CodeAnalysis.MemberNotNull("this.value")] + set => this.RaiseAndSetIfChanged(ref this.value, 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 c87c702..5ac107b 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveGeneratorTests.cs @@ -41,6 +41,33 @@ public partial class TestVM : ReactiveObject return TestHelper.TestPass(sourceCode); } + /// + /// Tests that the source generator correctly generates reactive properties. + /// + /// A task to monitor the async. + [Fact] + public Task FromReactivePropertiesCalledValue() + { + // Arrange: Setup the source code that matches the generator input expectations. + const string sourceCode = """ + using System; + using ReactiveUI; + using ReactiveUI.SourceGenerators; + using System.Reactive.Linq; + + namespace TestNs; + + public partial class TestVM : ReactiveObject + { + [Reactive] + private string value = string.Empty; + } + """; + + // Act: Initialize the helper and run the generator. Assert: Verify the generated code. + return TestHelper.TestPass(sourceCode); + } + /// /// Tests that the source generator correctly generates reactive properties. /// diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs index f24c8cb..bbfc266 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs @@ -19,6 +19,11 @@ public partial class TestClassOAPH_VM : ReactiveObject [Reactive] private bool _reactiveTestField; + [Reactive] +#pragma warning disable SX1309 // Field names should begin with underscore + private string value = string.Empty; +#pragma warning restore SX1309 // Field names should begin with underscore + /// /// Initializes a new instance of the class. /// diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs index f6537f1..2037c33 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs @@ -304,6 +304,12 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) return string.Empty; } + var fieldName = propertyInfo.FieldName; + if (propertyInfo.FieldName == "value") + { + fieldName = "this.value"; + } + var fieldSyntax = string.Empty; var partialModifier = propertyInfo.IsProperty ? "partial " : string.Empty; if (propertyInfo.IsProperty) @@ -318,13 +324,13 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) return $$""" {{fieldSyntax}} - /// + /// {{propertyAttributes}} {{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{partialModifier}}{{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; - [global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{propertyInfo.FieldName}}")] - {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); + [global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{fieldName}}")] + {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{fieldName}}, value); } """; } @@ -332,9 +338,9 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) return $$""" {{fieldSyntax}} - /// + /// {{propertyAttributes}} - {{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{partialModifier}}{{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); } + {{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{partialModifier}}{{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{fieldName}}, value); } """; } }