Skip to content

Feature Enable Partial Reactive Properties to have initializer #231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 11, 2025
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
42 changes: 42 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/InternalTestViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2025 ReactiveUI and contributors. All rights reserved.
// Licensed to the ReactiveUI and contributors under one or more agreements.
// The ReactiveUI and contributors licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using ReactiveUI;
using ReactiveUI.SourceGenerators;

namespace SGReactiveUI.SourceGenerators.Test;

internal partial class InternalTestViewModel : ReactiveObject
{
[Reactive]
public partial int PublicPartialPropertyTest { get; set; }

[Reactive]
public partial int PublicPartialPropertyWithInternalProtectedTest { get; protected internal set; }

[Reactive]
public partial int PublicPartialPropertyWithPrivateProtectedTest { get; private protected set; }

[Reactive]
public partial int PublicPartialPropertyWithProtectedTest { get; protected set; }

[Reactive]
public partial int PublicPartialPropertyWithInternalTest { get; internal set; }

[Reactive]
public partial int PublicPartialPropertyWithPrivateTest { get; private set; }

[Reactive]
internal partial int InternalPartialPropertyTest { get; set; }

[Reactive]
protected internal partial int InternalProtectedPartialPropertyTest { get; set; }

[Reactive]
protected partial int ProtectedPartialPropertyTest { get; set; }

[Reactive]
private partial int PrivatePartialPropertyTest { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<LangVersion>13.0</LangVersion>
<NoWarn>$(NoWarn);CA1812</NoWarn>
<PackageDescription>A MVVM framework that integrates with the Reactive Extensions for .NET to create elegant, testable User Interfaces that run on any mobile or desktop platform. This is the Source Generators package for ReactiveUI</PackageDescription>
</PropertyGroup>

Expand Down
3 changes: 3 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ public TestViewModel()
[ObservableAsProperty(InitialValue = "10")]
public partial int ObservableAsPropertyFromProperty { get; }

[Reactive]
internal partial int InternalPartialPropertyTest { get; set; }

[ObservableAsProperty]
private IObservable<object> ReferenceTypeObservable { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ internal sealed record PropertyInfo(
bool IsReferenceTypeOrUnconstrainedTypeParameter,
bool IncludeMemberNotNullOnSetAccessor,
EquatableArray<string> ForwardedAttributes,
string AccessModifier,
string SetAccessModifier,
string Inheritance,
string UseRequired,
bool IsProperty);
bool IsProperty,
string PropertyAccessModifier);
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ReactiveUI.SourceGenerators.Extensions;
using ReactiveUI.SourceGenerators.Helpers;
Expand Down Expand Up @@ -59,14 +60,60 @@ public sealed partial class ReactiveGenerator

token.ThrowIfCancellationRequested();

var accessModifier = $"{propertySymbol.SetMethod?.DeclaredAccessibility} set".ToLower();
if (accessModifier.StartsWith("public", StringComparison.Ordinal))
// Get Property AccessModifier.
var propertyAccessModifier = propertySymbol.DeclaredAccessibility.ToString().ToLower();
if (propertyAccessModifier?.Contains("and") == true)
{
accessModifier = "set";
propertyAccessModifier = propertyAccessModifier.Replace("and", " ");
}
else if (accessModifier.Contains("and"))
else if (propertyAccessModifier?.Contains("or") == true)
{
accessModifier = accessModifier.Replace("and", " ");
propertyAccessModifier = propertyAccessModifier.Replace("or", " ");
}

token.ThrowIfCancellationRequested();

// Get Set AccessModifier.
var setAccessModifier = $"{propertySymbol.SetMethod?.DeclaredAccessibility} set".ToLower();
if (setAccessModifier.StartsWith("public", StringComparison.Ordinal))
{
setAccessModifier = "set";
}
else if (setAccessModifier?.Contains("and") == true)
{
if (setAccessModifier.Contains("protectedandinternal"))
{
setAccessModifier = setAccessModifier.Replace("protectedandinternal", "private protected");
}
else
{
setAccessModifier = setAccessModifier.Replace("and", " ");
}
}
else if (setAccessModifier?.Contains("or") == true)
{
setAccessModifier = setAccessModifier.Replace("or", " ");
}

if (propertyAccessModifier == "private" && setAccessModifier == "private set")
{
setAccessModifier = "set";
}
else if (propertyAccessModifier == "internal" && setAccessModifier == "internal set")
{
setAccessModifier = "set";
}
else if (propertyAccessModifier == "protected" && setAccessModifier == "protected set")
{
setAccessModifier = "set";
}
else if (propertyAccessModifier == "protected internal" && setAccessModifier == "protected internal set")
{
setAccessModifier = "set";
}
else if (propertyAccessModifier == "private protected" && setAccessModifier == "private protected set")
{
setAccessModifier = "set";
}

token.ThrowIfCancellationRequested();
Expand All @@ -78,6 +125,12 @@ public sealed partial class ReactiveGenerator

var typeNameWithNullabilityAnnotations = propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
var fieldName = propertySymbol.GetGeneratedFieldName();

if (context.SemanticModel.Compilation is CSharpCompilation compilation && compilation.LanguageVersion == LanguageVersion.Preview)
{
fieldName = "field";
}

var propertyName = propertySymbol.Name;

// Get the nullability info for the property
Expand Down Expand Up @@ -111,10 +164,11 @@ public sealed partial class ReactiveGenerator
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributesString,
accessModifier,
setAccessModifier!,
inheritance,
useRequired,
true),
true,
propertyAccessModifier!),
builder.ToImmutable());
}
#endif
Expand Down Expand Up @@ -156,12 +210,12 @@ public sealed partial class ReactiveGenerator

// Get AccessModifier enum value from the attribute
attributeData.TryGetNamedArgument("SetModifier", out int accessModifierArgument);
var accessModifier = accessModifierArgument switch
var setAccessModifier = accessModifierArgument switch
{
1 => "protected set",
2 => "internal set",
3 => "private set",
4 => "internal protected set",
4 => "protected internal set",
5 => "private protected set",
6 => "init",
_ => "set",
Expand Down Expand Up @@ -236,10 +290,11 @@ public sealed partial class ReactiveGenerator
isReferenceTypeOrUnconstraindTypeParameter,
includeMemberNotNullOnSetAccessor,
forwardedAttributesString,
accessModifier,
setAccessModifier,
inheritance,
useRequired,
false),
false,
"public"),
builder.ToImmutable());
}

Expand All @@ -255,7 +310,7 @@ public sealed partial class ReactiveGenerator
private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, PropertyInfo[] properties)
{
// Get Parent class details from properties.ParentInfo
var (parentClassDeclarationsString, closingBrackets) = TargetInfo.GenerateParentClassDeclarations(properties.Select(p => p.TargetInfo.ParentInfo).ToArray());
var (parentClassDeclarationsString, closingBrackets) = TargetInfo.GenerateParentClassDeclarations([.. properties.Select(p => p.TargetInfo.ParentInfo)]);

var classes = GenerateClassWithProperties(containingTypeName, containingNamespace, containingClassVisibility, containingType, properties);

Expand Down Expand Up @@ -315,43 +370,47 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
return string.Empty;
}

var fieldName = propertyInfo.FieldName;
var setFieldName = propertyInfo.FieldName;
var getFieldName = propertyInfo.FieldName;
if (propertyInfo.FieldName == "value")
{
fieldName = "this.value";
setFieldName = "this.value";
}

var fieldSyntax = string.Empty;
var partialModifier = propertyInfo.IsProperty ? "partial " : string.Empty;
if (propertyInfo.IsProperty)
if (propertyInfo.IsProperty && propertyInfo.FieldName != "field")
{
fieldSyntax = $"private {propertyInfo.TypeNameWithNullabilityAnnotations} {propertyInfo.FieldName};";
}

var accessModifier = propertyInfo.PropertyAccessModifier;
var setAccessModifier = propertyInfo.SetAccessModifier;

var propertyAttributes = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage.Concat(propertyInfo.ForwardedAttributes));

if (propertyInfo.IncludeMemberNotNullOnSetAccessor || propertyInfo.IsReferenceTypeOrUnconstrainedTypeParameter)
{
return
$$"""
{{fieldSyntax}}
/// <inheritdoc cref="{{fieldName}}"/>
/// <inheritdoc cref="{{setFieldName}}"/>
{{propertyAttributes}}
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{partialModifier}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}
{{accessModifier}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{partialModifier}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}
{
get => {{propertyInfo.FieldName}};
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{fieldName}}")]
{{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{fieldName}}, value);
get => {{getFieldName}};
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{setFieldName}}")]
{{setAccessModifier}} => this.RaiseAndSetIfChanged(ref {{setFieldName}}, value);
}
""";
}

return
$$"""
{{fieldSyntax}}
/// <inheritdoc cref="{{fieldName}}"/>
/// <inheritdoc cref="{{setFieldName}}"/>
{{propertyAttributes}}
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{partialModifier}}{{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{fieldName}}, value); }
{{accessModifier}}{{propertyInfo.Inheritance}} {{partialModifier}}{{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{getFieldName}}; {{setAccessModifier}} => this.RaiseAndSetIfChanged(ref {{setFieldName}}, value); }
""";
}
}
Loading