Skip to content

Commit 0910397

Browse files
authored
Feature Set Access Modifier for [Reactive] (#67)
* Feature Set Access Modifier for [Reactive] Closes #64 * Update README.md
1 parent 4419d33 commit 0910397

File tree

8 files changed

+105
-7
lines changed

8 files changed

+105
-7
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,17 @@ public partial class MyReactiveClass : ReactiveObject
7373
}
7474
```
7575

76+
### Usage Reactive property with set Access Modifier
77+
```csharp
78+
using ReactiveUI.SourceGenerators;
79+
80+
public partial class MyReactiveClass : ReactiveObject
81+
{
82+
[Reactive(SetModifier = AccessModifier.Protected)]
83+
private string _myProperty;
84+
}
85+
```
86+
7687
## Usage ObservableAsPropertyHelper `[ObservableAsProperty]`
7788

7889
### Usage ObservableAsPropertyHelper with Field

src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Reactive.Linq;
88
using System.Runtime.Serialization;
99
using System.Text.Json.Serialization;
10+
using System.Windows.Media.TextFormatting;
1011
using ReactiveUI;
1112
using ReactiveUI.SourceGenerators;
1213

@@ -26,7 +27,7 @@ public partial class TestViewModel : ReactiveObject
2627
private double _test2Property = 1.1d;
2728

2829
[JsonInclude]
29-
[Reactive]
30+
[Reactive(SetModifier = AccessModifier.Protected)]
3031
[DataMember]
3132
private int _test1Property = 10;
3233

src/ReactiveUI.SourceGenerators.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
<Project Path="TestApps\TestMauiApplication\TestMauiApplication.csproj">
2424
<Deploy />
2525
</Project>
26+
<Project Path="D:\Projects\GitHub\ReactiveUI\ReactiveUI.SourceGenerators\src\TestApps\TestAvaloniaApplication.Desktop\TestAvaloniaApplication.Desktop.csproj" />
27+
<Project Path="D:\Projects\GitHub\ReactiveUI\ReactiveUI.SourceGenerators\src\TestApps\TestMauiApplication\TestMauiApplication.csproj">
28+
<Deploy />
29+
</Project>
2630
</Folder>
2731
<Properties Name="Visual Studio">
2832
<Property Name="OpenWith" Value="17" />

src/ReactiveUI.SourceGenerators/Core/Helpers/AttributeDefinitions.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,34 @@ internal static class AttributeDefinitions
1212
public const string GeneratedCode = "global::System.CodeDom.Compiler.GeneratedCode";
1313
public const string ExcludeFromCodeCoverage = "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage";
1414
public const string Obsolete = "global::System.Obsolete";
15+
16+
public const string AccessModifierType = "ReactiveUI.SourceGenerators.AccessModifier";
17+
public const string AccessModifierEnum =
18+
"""
19+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
20+
// Licensed to the .NET Foundation under one or more agreements.
21+
// The .NET Foundation licenses this file to you under the MIT license.
22+
// See the LICENSE file in the project root for full license information.
23+
24+
// <auto-generated/>
25+
#pragma warning disable
26+
#nullable enable
27+
namespace ReactiveUI.SourceGenerators;
28+
29+
/// <summary>
30+
/// AccessModifier.
31+
/// </summary>
32+
internal enum AccessModifier
33+
{
34+
Public,
35+
Protected,
36+
Internal,
37+
Private,
38+
InternalProtected,
39+
PrivateProtected,
40+
}
41+
""";
42+
1543
public const string ReactiveObjectAttribute = """
1644
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
1745
// Licensed to the .NET Foundation under one or more agreements.
@@ -90,7 +118,16 @@ namespace ReactiveUI.SourceGenerators;
90118
/// <seealso cref="Attribute" />
91119
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveGenerator", "1.1.0.0")]
92120
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
93-
internal sealed class ReactiveAttribute : Attribute;
121+
internal sealed class ReactiveAttribute : Attribute
122+
{
123+
/// <summary>
124+
/// Gets the AccessModifier of the set property.
125+
/// </summary>
126+
/// <value>
127+
/// The AccessModifier of the set property.
128+
/// </value>
129+
public AccessModifier SetModifier { get; init; }
130+
}
94131
#nullable restore
95132
#pragma warning restore
96133
""";

src/ReactiveUI.SourceGenerators/ObservableAsProperty/ObservableAsPropertyGenerator.Execute.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,8 @@ internal static bool GetFieldInfoFromClass(
272272
initializer,
273273
isReferenceTypeOrUnconstraindTypeParameter,
274274
includeMemberNotNullOnSetAccessor,
275-
forwardedAttributes.ToImmutable());
275+
forwardedAttributes.ToImmutable(),
276+
"public");
276277

277278
diagnostics = builder.ToImmutable();
278279

src/ReactiveUI.SourceGenerators/Reactive/Models/PropertyInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// The .NET Foundation licenses this file to you under the MIT license.
44
// See the LICENSE file in the project root for full license information.
55

6+
using Microsoft.CodeAnalysis.CSharp;
67
using Microsoft.CodeAnalysis.CSharp.Syntax;
78
using ReactiveUI.SourceGenerators.Helpers;
89

@@ -18,4 +19,5 @@ internal sealed record PropertyInfo(
1819
EqualsValueClauseSyntax? Initializer,
1920
bool IsReferenceTypeOrUnconstraindTypeParameter,
2021
bool IncludeMemberNotNullOnSetAccessor,
21-
EquatableArray<AttributeInfo> ForwardedAttributes);
22+
EquatableArray<AttributeInfo> ForwardedAttributes,
23+
string AccessModifier);

src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.Execute.cs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,22 @@ internal static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyI
8080
setterFieldExpression,
8181
IdentifierName("value"))));
8282

83+
SyntaxToken[] syntaxKinds = propertyInfo.AccessModifier switch
84+
{
85+
"public" => [],
86+
"protected" => [Token(SyntaxKind.ProtectedKeyword)],
87+
"internal" => [Token(SyntaxKind.InternalKeyword)],
88+
"private" => [Token(SyntaxKind.PrivateKeyword)],
89+
"internal protected" => [Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.ProtectedKeyword)],
90+
"private protected" => [Token(SyntaxKind.PrivateKeyword), Token(SyntaxKind.ProtectedKeyword)],
91+
_ => [],
92+
};
93+
8394
// Create the setter for the generated property:
84-
//
95+
// Literal(propertyInfo.AccessModifier)
8596
// set => this.RaiseAndSetIfChanged(ref <FIELD_NAME>, value);
8697
var setAccessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
98+
.AddModifiers(syntaxKinds)
8799
.WithExpressionBody(ArrowExpressionClause(ParseExpression($"this.RaiseAndSetIfChanged(ref {getterFieldIdentifierName}, {IdentifierName("value")});")));
88100

89101
// Add the [MemberNotNull] attribute if needed:
@@ -168,6 +180,29 @@ internal static bool GetFieldInfoFromClass(
168180
var propertyName = GetGeneratedPropertyName(fieldSymbol);
169181
var initializer = fieldSyntax.Declaration.Variables.FirstOrDefault()?.Initializer;
170182

183+
if (!fieldSymbol.TryGetAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveAttributeType, out var attributeData1))
184+
{
185+
propertyInfo = null;
186+
diagnostics = builder.ToImmutable();
187+
188+
return false;
189+
}
190+
191+
token.ThrowIfCancellationRequested();
192+
193+
// Get AccessModifier enum value from the attribute
194+
attributeData1.TryGetNamedArgument("SetModifier", out int? accessModifierArgument);
195+
var accessModifier = accessModifierArgument switch
196+
{
197+
0 => "public",
198+
1 => "protected",
199+
2 => "internal",
200+
3 => "private",
201+
4 => "internal protected",
202+
5 => "private protected",
203+
_ => "public",
204+
};
205+
171206
// Check for name collisions
172207
if (fieldName == propertyName)
173208
{
@@ -298,7 +333,8 @@ internal static bool GetFieldInfoFromClass(
298333
initializer,
299334
isReferenceTypeOrUnconstraindTypeParameter,
300335
includeMemberNotNullOnSetAccessor,
301-
forwardedAttributes.ToImmutable());
336+
forwardedAttributes.ToImmutable(),
337+
accessModifier);
302338

303339
diagnostics = builder.ToImmutable();
304340

src/ReactiveUI.SourceGenerators/Reactive/ReactiveGenerator.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ public sealed partial class ReactiveGenerator : IIncrementalGenerator
2727
public void Initialize(IncrementalGeneratorInitializationContext context)
2828
{
2929
context.RegisterPostInitializationOutput(ctx =>
30-
ctx.AddSource($"{AttributeDefinitions.ReactiveAttributeType}.g.cs", SourceText.From(AttributeDefinitions.ReactiveAttribute, Encoding.UTF8)));
30+
{
31+
// Add the AccessModifier enum to the compilation
32+
ctx.AddSource($"{AttributeDefinitions.AccessModifierType}.g.cs", SourceText.From(AttributeDefinitions.AccessModifierEnum, Encoding.UTF8));
33+
34+
// Add the ReactiveAttribute to the compilation
35+
ctx.AddSource($"{AttributeDefinitions.ReactiveAttributeType}.g.cs", SourceText.From(AttributeDefinitions.ReactiveAttribute, Encoding.UTF8));
36+
});
3137

3238
// Gather info for all annotated command methods (starting from method declarations with at least one attribute)
3339
IncrementalValuesProvider<(HierarchyInfo Hierarchy, Result<PropertyInfo> Info)> propertyInfoWithErrors =

0 commit comments

Comments
 (0)