Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,29 @@ public partial class MyReactiveClass : ReactiveObject
}
```

### Usage ObservableAsPropertyHelper with Field and non readonly nullable OAPH field
```csharp

### Usage ObservableAsPropertyHelper with Observable Property
```csharp
using ReactiveUI.SourceGenerators;

public partial class MyReactiveClass : ReactiveObject
{
public partial class MyReactiveClass : ReactiveObject, IActivatableViewModel
{
[ObservableAsProperty(ReadOnly = false)]
private string _myProperty = "Default Value";

public MyReactiveClass()
{
// default value for MyObservableProperty prior to initialization.
_myObservable = "Test Value Pre Init";

// Initialize generated _myObservablePropertyHelper
// for the generated MyObservableProperty
InitializeOAPH();
this.WhenActivated(disposables =>
{
_myPrpertyHelper = MyPropertyObservable()
.ToProperty(this, x => x.MyProperty)
.DisposeWith(disposables);
});
}

[ObservableAsProperty]
IObservable<string> MyObservable => Observable.Return("Test Value");
IObservable<string> MyPropertyObservable() => Observable.Return("Test Value");
}
```

Expand Down
23 changes: 22 additions & 1 deletion src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// See the LICENSE file in the project root for full license information.

using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Runtime.Serialization;
Expand All @@ -16,8 +17,11 @@ namespace SGReactiveUI.SourceGenerators.Test;
/// <summary>
/// TestClass.
/// </summary>
/// <seealso cref="ReactiveUI.ReactiveObject" />
/// <seealso cref="ReactiveUI.IActivatableViewModel" />
/// <seealso cref="System.IDisposable" />
[DataContract]
public partial class TestViewModel : ReactiveObject, IDisposable
public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDisposable
{
private readonly IObservable<bool> _observable = Observable.Return(true);
private readonly Subject<double?> _testSubject = new();
Expand All @@ -28,6 +32,12 @@ public partial class TestViewModel : ReactiveObject, IDisposable
[ObservableAsProperty]
private double? _test2Property = 1.1d;

[ObservableAsProperty(ReadOnly = false)]
private double? _test11Property = 11.1d;

[Reactive]
private double? _test12Property = 12.1d;

[JsonInclude]
[Reactive(SetModifier = AccessModifier.Protected)]
[DataMember]
Expand All @@ -39,6 +49,12 @@ public partial class TestViewModel : ReactiveObject, IDisposable
/// </summary>
public TestViewModel()
{
this.WhenActivated(disposables =>
{
Console.Out.WriteLine("Activated");
_test11PropertyHelper = this.WhenAnyValue(x => x.Test12Property).ToProperty(this, x => x.Test11Property, out _).DisposeWith(disposables);
});

Console.Out.WriteLine("MyReadOnlyProperty before init");

// only settable prior to init, after init it will be ignored.
Expand Down Expand Up @@ -201,6 +217,11 @@ public TestViewModel()
[ObservableAsProperty]
public IObservable<int> ObservableAsPropertyTest2 => Observable.Return(9);

/// <summary>
/// Gets the Activator which will be used by the View when Activation/Deactivation occurs.
/// </summary>
public ViewModelActivator Activator { get; } = new();

/// <summary>
/// Gets observables as property test.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ internal sealed class ObservableAsPropertyAttribute : Attribute
/// The name of the property.
/// </value>
public string? PropertyName { get; init; }

/// <summary>
/// Gets the Readonly state of the OAPH property.
/// </summary>
/// <value>
/// The is read only of the OAPH property.
/// </value>
public bool ReadOnly { get; init; } = true;
}
#nullable restore
#pragma warning restore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
Expand Down Expand Up @@ -79,6 +80,19 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
.ToImmutableArray();

var modifiers = new List<SyntaxToken>();
var helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>";
if (propertyInfo.AccessModifier == "readonly")
{
modifiers.Add(Token(SyntaxKind.PrivateKeyword));
modifiers.Add(Token(SyntaxKind.ReadOnlyKeyword));
}
else
{
helperTypeName = $"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>?";
modifiers.Add(Token(SyntaxKind.PrivateKeyword));
}

// Construct the generated property as follows:
//
// /// <inheritdoc cref="<FIELD_NAME>"/>
Expand All @@ -91,7 +105,7 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
// }
return
ImmutableArray.Create<MemberDeclarationSyntax>(
FieldDeclaration(VariableDeclaration(ParseTypeName($"ReactiveUI.ObservableAsPropertyHelper<{propertyType}>")))
FieldDeclaration(VariableDeclaration(ParseTypeName(helperTypeName)))
.AddDeclarationVariables(VariableDeclarator(getterFieldIdentifierName + "Helper"))
.AddAttributeLists(
AttributeList(SingletonSeparatedList(
Expand All @@ -100,9 +114,7 @@ internal static ImmutableArray<MemberDeclarationSyntax> GetPropertySyntax(Proper
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableAsPropertyGenerator).Assembly.GetName().Version.ToString()))))))
.WithOpenBracketToken(Token(TriviaList(Comment($"/// <inheritdoc cref=\"{propertyInfo.FieldName + "Helper"}\"/>")), SyntaxKind.OpenBracketToken, TriviaList())))
.AddModifiers(
Token(SyntaxKind.PrivateKeyword),
Token(SyntaxKind.ReadOnlyKeyword)),
.AddModifiers([.. modifiers]),
PropertyDeclaration(propertyType, Identifier(propertyInfo.PropertyName))
.AddAttributeLists(
AttributeList(SingletonSeparatedList(
Expand All @@ -124,6 +136,7 @@ internal static bool GetFieldInfoFromClass(
FieldDeclarationSyntax fieldSyntax,
IFieldSymbol fieldSymbol,
SemanticModel semanticModel,
bool? isReadonly,
CancellationToken token,
[NotNullWhen(true)] out PropertyInfo? propertyInfo,
out ImmutableArray<DiagnosticInfo> diagnostics)
Expand Down Expand Up @@ -282,7 +295,7 @@ internal static bool GetFieldInfoFromClass(
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributes.ToImmutable(),
"public");
isReadonly == false ? string.Empty : "readonly");

diagnostics = builder.ToImmutable();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
static (node, _) => node is VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: FieldDeclarationSyntax { Parent: ClassDeclarationSyntax or RecordDeclarationSyntax, AttributeLists.Count: > 0 } } },
static (context, token) =>
{
var symbol = ModelExtensions.GetDeclaredSymbol(context.SemanticModel, context.TargetNode, token)!;
token.ThrowIfCancellationRequested();

// Skip symbols without the target attribute
if (!symbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ObservableAsPropertyAttributeType, out var attributeData))
{
return default;
}

// Get the can PropertyName member, if any
attributeData.TryGetNamedArgument("ReadOnly", out bool? isReadonly);

var fieldDeclaration = (FieldDeclarationSyntax)context.TargetNode.Parent!.Parent!;
var fieldSymbol = (IFieldSymbol)context.TargetSymbol;

Expand All @@ -43,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

token.ThrowIfCancellationRequested();

Execute.GetFieldInfoFromClass(fieldDeclaration, fieldSymbol, context.SemanticModel, token, out var propertyInfo, out var diagnostics);
Execute.GetFieldInfoFromClass(fieldDeclaration, fieldSymbol, context.SemanticModel, isReadonly, token, out var propertyInfo, out var diagnostics);

token.ThrowIfCancellationRequested();
return (Hierarchy: hierarchy, new Result<PropertyInfo?>(propertyInfo, diagnostics));
Expand Down