diff --git a/README.md b/README.md
index 2027d2b..d45b0ed 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,17 @@ public partial class MyReactiveClass : ReactiveObject
}
```
+### Usage Reactive property with set Access Modifier
+```csharp
+using ReactiveUI.SourceGenerators;
+
+public partial class MyReactiveClass : ReactiveObject
+{
+ [Reactive(SetModifier = AccessModifier.Protected)]
+ private string _myProperty;
+}
+```
+
## Usage ObservableAsPropertyHelper `[ObservableAsProperty]`
### Usage ObservableAsPropertyHelper with Field
diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
index e5cacd3..245e4c2 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
@@ -7,6 +7,7 @@
using System.Reactive.Linq;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
+using System.Windows.Media.TextFormatting;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
@@ -26,7 +27,7 @@ public partial class TestViewModel : ReactiveObject
private double _test2Property = 1.1d;
[JsonInclude]
- [Reactive]
+ [Reactive(SetModifier = AccessModifier.Protected)]
[DataMember]
private int _test1Property = 10;
diff --git a/src/ReactiveUI.SourceGenerators.slnx b/src/ReactiveUI.SourceGenerators.slnx
index 4adf8d0..752cecd 100644
--- a/src/ReactiveUI.SourceGenerators.slnx
+++ b/src/ReactiveUI.SourceGenerators.slnx
@@ -23,6 +23,10 @@
+
+
+
+
diff --git a/src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs
index 8ae6dc2..3e4692d 100644
--- a/src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs
+++ b/src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs
@@ -12,6 +12,34 @@ internal static class AttributeDefinitions
public const string GeneratedCode = "global::System.CodeDom.Compiler.GeneratedCode";
public const string ExcludeFromCodeCoverage = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage";
public const string Obsolete = "global::System.Obsolete";
+
+ public const string AccessModifierType = "ReactiveUI.SourceGenerators.AccessModifier";
+ public const string AccessModifierEnum =
+ """
+ // 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,
+ }
+ """;
+
public const string ReactiveObjectAttribute = """
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
@@ -90,7 +118,16 @@ namespace ReactiveUI.SourceGenerators;
///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveGenerator", "1.1.0.0")]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
-internal sealed class ReactiveAttribute : Attribute;
+internal sealed class ReactiveAttribute : Attribute
+{
+ ///
+ /// Gets the AccessModifier of the set property.
+ ///
+ ///
+ /// The AccessModifier of the set property.
+ ///
+ public AccessModifier SetModifier { get; init; }
+}
#nullable restore
#pragma warning restore
""";
diff --git a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs
index af1d4b1..05617fa 100644
--- a/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs
@@ -272,7 +272,8 @@ internal static bool GetFieldInfoFromClass(
initializer,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
- forwardedAttributes.ToImmutable());
+ forwardedAttributes.ToImmutable(),
+ "public");
diagnostics = builder.ToImmutable();
diff --git a/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs b/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs
index c39ba59..500e933 100644
--- a/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs
+++ b/src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs
@@ -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 Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ReactiveUI.SourceGenerators.Helpers;
@@ -18,4 +19,5 @@ internal sealed record PropertyInfo(
EqualsValueClauseSyntax? Initializer,
bool IsReferenceTypeOrUnconstraindTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
- EquatableArray ForwardedAttributes);
+ EquatableArray ForwardedAttributes,
+ string AccessModifier);
diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
index 35cd2c8..8d024d8 100644
--- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs
@@ -80,10 +80,22 @@ internal static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyI
setterFieldExpression,
IdentifierName("value"))));
+ SyntaxToken[] syntaxKinds = propertyInfo.AccessModifier switch
+ {
+ "public" => [],
+ "protected" => [Token(SyntaxKind.ProtectedKeyword)],
+ "internal" => [Token(SyntaxKind.InternalKeyword)],
+ "private" => [Token(SyntaxKind.PrivateKeyword)],
+ "internal protected" => [Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.ProtectedKeyword)],
+ "private protected" => [Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ProtectedKeyword)],
+ _ => [],
+ };
+
// Create the setter for the generated property:
- //
+ // Literal(propertyInfo.AccessModifier)
// set => this.RaiseAndSetIfChanged(ref , value);
var setAccessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
+ .AddModifiers(syntaxKinds)
.WithExpressionBody(ArrowExpressionClause(ParseExpression($"this.RaiseAndSetIfChanged(ref {getterFieldIdentifierName}, {IdentifierName("value")});")));
// Add the [MemberNotNull] attribute if needed:
@@ -168,6 +180,29 @@ internal static bool GetFieldInfoFromClass(
var propertyName = GetGeneratedPropertyName(fieldSymbol);
var initializer = fieldSyntax.Declaration.Variables.FirstOrDefault()?.Initializer;
+ if (!fieldSymbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveAttributeType, out var attributeData1))
+ {
+ propertyInfo = null;
+ diagnostics = builder.ToImmutable();
+
+ return false;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Get AccessModifier enum value from the attribute
+ attributeData1.TryGetNamedArgument("SetModifier", out int? accessModifierArgument);
+ var accessModifier = accessModifierArgument switch
+ {
+ 0 => "public",
+ 1 => "protected",
+ 2 => "internal",
+ 3 => "private",
+ 4 => "internal protected",
+ 5 => "private protected",
+ _ => "public",
+ };
+
// Check for name collisions
if (fieldName == propertyName)
{
@@ -298,7 +333,8 @@ internal static bool GetFieldInfoFromClass(
initializer,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
- forwardedAttributes.ToImmutable());
+ forwardedAttributes.ToImmutable(),
+ accessModifier);
diagnostics = builder.ToImmutable();
diff --git a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs
index 382a62d..252fad9 100644
--- a/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs
@@ -27,7 +27,13 @@ public sealed partial class ReactiveGenerator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx =>
- ctx.AddSource($"{AttributeDefinitions.ReactiveAttributeType}.g.cs", SourceText.From(AttributeDefinitions.ReactiveAttribute, Encoding.UTF8)));
+ {
+ // Add the AccessModifier enum to the compilation
+ ctx.AddSource($"{AttributeDefinitions.AccessModifierType}.g.cs", SourceText.From(AttributeDefinitions.AccessModifierEnum, Encoding.UTF8));
+
+ // Add the ReactiveAttribute to the compilation
+ ctx.AddSource($"{AttributeDefinitions.ReactiveAttributeType}.g.cs", SourceText.From(AttributeDefinitions.ReactiveAttribute, Encoding.UTF8));
+ });
// Gather info for all annotated command methods (starting from method declarations with at least one attribute)
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result Info)> propertyInfoWithErrors =