Skip to content

Commit

Permalink
Feature Set Access Modifier for [Reactive] (#67)
Browse files Browse the repository at this point in the history
* Feature Set Access Modifier for [Reactive]

Closes #64

* Update README.md
  • Loading branch information
ChrisPulman authored Sep 27, 2024
1 parent 4419d33 commit 0910397
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 7 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down
4 changes: 4 additions & 0 deletions src/ReactiveUI.SourceGenerators.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<Project Path="TestApps\TestMauiApplication\TestMauiApplication.csproj">
<Deploy />
</Project>
<Project Path="D:\Projects\GitHub\ReactiveUI\ReactiveUI.SourceGenerators\src\TestApps\TestAvaloniaApplication.Desktop\TestAvaloniaApplication.Desktop.csproj" />
<Project Path="D:\Projects\GitHub\ReactiveUI\ReactiveUI.SourceGenerators\src\TestApps\TestMauiApplication\TestMauiApplication.csproj">
<Deploy />
</Project>
</Folder>
<Properties Name="Visual Studio">
<Property Name="OpenWith" Value="17" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace ReactiveUI.SourceGenerators;
/// <summary>
/// AccessModifier.
/// </summary>
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.
Expand Down Expand Up @@ -90,7 +118,16 @@ namespace ReactiveUI.SourceGenerators;
/// <seealso cref="Attribute" />
[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
{
/// <summary>
/// Gets the AccessModifier of the set property.
/// </summary>
/// <value>
/// The AccessModifier of the set property.
/// </value>
public AccessModifier SetModifier { get; init; }
}
#nullable restore
#pragma warning restore
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ internal static bool GetFieldInfoFromClass(
initializer,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributes.ToImmutable());
forwardedAttributes.ToImmutable(),
"public");

diagnostics = builder.ToImmutable();

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 Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ReactiveUI.SourceGenerators.Helpers;

Expand All @@ -18,4 +19,5 @@ internal sealed record PropertyInfo(
EqualsValueClauseSyntax? Initializer,
bool IsReferenceTypeOrUnconstraindTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
EquatableArray<AttributeInfo> ForwardedAttributes);
EquatableArray<AttributeInfo> ForwardedAttributes,
string AccessModifier);
Original file line number Diff line number Diff line change
Expand Up @@ -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 <FIELD_NAME>, value);
var setAccessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.AddModifiers(syntaxKinds)
.WithExpressionBody(ArrowExpressionClause(ParseExpression($"this.RaiseAndSetIfChanged(ref {getterFieldIdentifierName}, {IdentifierName("value")});")));

// Add the [MemberNotNull] attribute if needed:
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -298,7 +333,8 @@ internal static bool GetFieldInfoFromClass(
initializer,
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributes.ToImmutable());
forwardedAttributes.ToImmutable(),
accessModifier);

diagnostics = builder.ToImmutable();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PropertyInfo> Info)> propertyInfoWithErrors =
Expand Down

0 comments on commit 0910397

Please sign in to comment.