diff --git a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs index 71a06ba..f1be4f2 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/OAPH/OAPFromObservableGeneratorTests.FromPartialProperty#TestNs.TestVM.ObservableAsPropertyFromObservable.g.verified.cs @@ -16,7 +16,7 @@ public partial class TestVM private double? _testProperty; /// - private ReactiveUI.ObservableAsPropertyHelper? _testPropertyHelper; + private readonly ReactiveUI.ObservableAsPropertyHelper _testPropertyHelper; /// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs new file mode 100644 index 0000000..f24c8cb --- /dev/null +++ b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs @@ -0,0 +1,51 @@ +// 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 ReactiveUI; +using ReactiveUI.SourceGenerators; + +namespace SGReactiveUI.SourceGenerators.Test; + +/// +/// TestClassOAPH VM. +/// +public partial class TestClassOAPH_VM : ReactiveObject +{ + [ObservableAsProperty] + private bool _observableTestField; + + [Reactive] + private bool _reactiveTestField; + + /// + /// Initializes a new instance of the class. + /// + public TestClassOAPH_VM() + { + _observableTestPropertyHelper = this.WhenAnyValue(x => x.ReactiveTestProperty) + .ToProperty(this, x => x.ObservableTestProperty); + + _observableTestFieldHelper = this.WhenAnyValue(x => x.ReactiveTestField) + .ToProperty(this, x => x.ObservableTestField); + } + + /// + /// Gets a value indicating whether [observable test property]. + /// + /// + /// true if [observable test property]; otherwise, false. + /// + [ObservableAsProperty] + public partial bool ObservableTestProperty { get; } + + /// + /// Gets or sets a value indicating whether [reactive test property]. + /// + /// + /// true if [reactive test property]; otherwise, false. + /// + [Reactive] + public partial bool ReactiveTestProperty { get; set; } +} diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index a6d9504..65b013e 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -70,6 +70,10 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis [Reactive] private IEnumerable _people = [new Person()]; + [Reactive] + private double? _myDoubleProperty; + [Reactive] + private double _myDoubleNonNullProperty; [BindableDerivedList] private ReadOnlyObservableCollection? _visiblePeople; diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs index 2afb46c..90b221d 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/Models/ObservableMethodInfo.cs @@ -19,6 +19,7 @@ internal record ObservableMethodInfo( bool IsNullableType, bool IsProperty, EquatableArray ForwardedPropertyAttributes, + string IsReadOnly, string AccessModifier, string? InitialValue) { diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs index 662a649..7b3b47c 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.Execute.cs @@ -41,6 +41,11 @@ public sealed partial class ObservableAsPropertyGenerator attributeData.TryGetNamedArgument("UseProtected", out bool useProtected); var useProtectedModifier = useProtected ? "protected" : "private"; + token.ThrowIfCancellationRequested(); + + // Get the can ReadOnly member, if any + attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly); + token.ThrowIfCancellationRequested(); var compilation = context.SemanticModel.Compilation; @@ -106,6 +111,7 @@ public sealed partial class ObservableAsPropertyGenerator isNullableType, false, propertyAttributes, + string.Empty, useProtectedModifier, initialValue), diagnostics.ToImmutable()); @@ -131,6 +137,7 @@ public sealed partial class ObservableAsPropertyGenerator var observableType = string.Empty; var isNullableType = false; + var isPartialProperty = false; token.ThrowIfCancellationRequested(); context.GetForwardedAttributes( @@ -173,10 +180,9 @@ public sealed partial class ObservableAsPropertyGenerator token.ThrowIfCancellationRequested(); - var inheritance = propertySymbol.IsVirtual ? " virtual" : propertySymbol.IsOverride ? " override" : string.Empty; + isPartialProperty = true; - // Get the can ReadOnly member, if any - attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly); + var inheritance = propertySymbol.IsVirtual ? " virtual" : propertySymbol.IsOverride ? " override" : string.Empty; token.ThrowIfCancellationRequested(); @@ -215,6 +221,12 @@ public sealed partial class ObservableAsPropertyGenerator } #endif + var isReadOnlyString = string.Empty; + if (isPartialProperty) + { + isReadOnlyString = isReadonly == false ? string.Empty : "readonly"; + } + // Get the containing type info var targetInfo = TargetInfo.From(propertySymbol.ContainingType); @@ -229,6 +241,7 @@ public sealed partial class ObservableAsPropertyGenerator isNullableType, true, propertyAttributes, + isReadOnlyString, useProtectedModifier, initialValue), diagnostics.ToImmutable()); @@ -307,12 +320,20 @@ private static string GetPropertySyntax(ObservableMethodInfo propertyInfo) propertyType = propertyInfo.PartialPropertyType; } + var helperTypeName = $"{propertyInfo.AccessModifier} ReactiveUI.ObservableAsPropertyHelper<{propertyType}>?"; + + // If the property is readonly, we need to change the helper to be non-nullable + if (propertyInfo.IsReadOnly == "readonly") + { + helperTypeName = $"{propertyInfo.AccessModifier} readonly ReactiveUI.ObservableAsPropertyHelper<{propertyType}>"; + } + return $$""" /// private {{propertyType}} {{getterFieldIdentifierName}}{{initialValue}}; /// - {{propertyInfo.AccessModifier}} ReactiveUI.ObservableAsPropertyHelper<{{propertyType}}>? {{getterFieldIdentifierName}}Helper; + {{helperTypeName}} {{getterFieldIdentifierName}}Helper; /// {{propertyAttributes}} diff --git a/src/ReactiveUI.SourceGenerators.slnx b/src/ReactiveUI.SourceGenerators.slnx deleted file mode 100644 index 752cecd..0000000 --- a/src/ReactiveUI.SourceGenerators.slnx +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file