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
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