Skip to content

Commit

Permalink
Add Required Init For Reactive Property
Browse files Browse the repository at this point in the history
Closes #134
  • Loading branch information
ChrisPulman committed Jan 7, 2025
1 parent 45490dc commit a271660
Show file tree
Hide file tree
Showing 21 changed files with 201 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//HintName: ReactiveUI.SourceGenerators.AccessModifier.g.cs
// Copyright (c) 2025 .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,
Init,
}

/// <summary>
/// InheritanceModifier.
/// </summary>
internal enum InheritanceModifier
{
None,
Virtual,
Override,
New,
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//HintName: ReactiveUI.SourceGenerators.ReactiveAttribute.g.cs
// Copyright (c) 2025 .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.

using System;

// <auto-generated/>
#pragma warning disable
#nullable enable
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
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; }

/// <summary>
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//HintName: TestNs.TestVM.Properties.g.cs
// <auto-generated/>
using ReactiveUI;

#pragma warning disable
#nullable enable

namespace TestNs
{
/// <summary>
/// Partial class for the TestVM which contains ReactiveUI Reactive property initialization.
/// </summary>
public partial class TestVM
{
/// <inheritdoc cref="_mustBeSet"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public string MustBeSet
{
get => _mustBeSet;
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_mustBeSet")]
set => this.RaiseAndSetIfChanged(ref _mustBeSet, value);
}
}
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }

/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<LangVersion>12.0</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<NoWarn>$(NoWarn);CA1812;CA1001</NoWarn>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,31 @@ public partial class TestInnerClass3 : ReactiveObject
// Act: Initialize the helper and run the generator. Assert: Verify the generated code.
return TestHelper.TestPass(sourceCode);
}

/// <summary>
/// Tests that the source generator correctly generates reactive properties.
/// </summary>
/// <returns>A task to monitor the async.</returns>
[Fact]
public Task FromReactivePropertiesWithInit()
{
// Arrange: Setup the source code that matches the generator input expectations.
const string sourceCode = """
using System;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using System.Reactive.Linq;
namespace TestNs;
public partial class TestVM : ReactiveObject
{
[Reactive(SetModifier = AccessModifier.Init, UseRequired = true)]
private string _mustBeSet;
}
""";

// Act: Initialize the helper and run the generator. Assert: Verify the generated code.
return TestHelper.TestPass(sourceCode);
}
}
6 changes: 6 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
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 System.Diagnostics.CodeAnalysis;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
Expand Down Expand Up @@ -56,11 +57,16 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
[Reactive(Inheritance = InheritanceModifier.Virtual, SetModifier = AccessModifier.Protected)]
private string? _name;

[Reactive(SetModifier = AccessModifier.Init, UseRequired = true)]
private string _mustBeSet;

/// <summary>
/// Initializes a new instance of the <see cref="TestViewModel"/> class.
/// </summary>
[SetsRequiredMembers]
public TestViewModel()
{
MustBeSet = "Test";
this.WhenActivated(disposables =>
{
Console.Out.WriteLine("Activated");
Expand Down
6 changes: 6 additions & 0 deletions src/ReactiveUI.SourceGenerators/AttributeDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ internal enum AccessModifier
Private,
InternalProtected,
PrivateProtected,
Init,
}
/// <summary>
Expand Down Expand Up @@ -155,6 +156,11 @@ internal sealed class ReactiveAttribute : Attribute
/// Gets the InheritanceModifier of the property.
/// </sumary>
public InheritanceModifier Inheritance { get; init; }
/// <summary>
/// Use Required attribute to indicate that the property is required.
/// </summary>
public bool UseRequired { get; init; }
}
#nullable restore
#pragma warning restore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ internal sealed record PropertyInfo(
bool IncludeMemberNotNullOnSetAccessor,
EquatableArray<string> ForwardedAttributes,
string AccessModifier,
string Inheritance);
string Inheritance,
string UseRequired);
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,17 @@ public sealed partial class ReactiveGenerator
attributeData.TryGetNamedArgument("SetModifier", out int accessModifierArgument);
var accessModifier = accessModifierArgument switch
{
1 => "protected ",
2 => "internal ",
3 => "private ",
4 => "internal protected ",
5 => "private protected ",
_ => string.Empty,
1 => "protected set",
2 => "internal set",
3 => "private set",
4 => "internal protected set",
5 => "private protected set",
6 => "init",
_ => "set",
};

token.ThrowIfCancellationRequested();

// Get Inheritance value from the attribute
attributeData.TryGetNamedArgument("Inheritance", out int inheritanceArgument);
var inheritance = inheritanceArgument switch
Expand All @@ -87,6 +90,12 @@ public sealed partial class ReactiveGenerator

token.ThrowIfCancellationRequested();

// Get Inheritance value from the attribute
attributeData.TryGetNamedArgument("UseRequired", out bool useRequiredArgument);
var useRequired = useRequiredArgument ? "required " : string.Empty;

token.ThrowIfCancellationRequested();

// Get the property type and name
var typeNameWithNullabilityAnnotations = fieldSymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations();
var fieldName = fieldSymbol.Name;
Expand Down Expand Up @@ -219,7 +228,8 @@ public sealed partial class ReactiveGenerator
includeMemberNotNullOnSetAccessor,
forwardedAttributesString,
accessModifier,
inheritance),
inheritance,
useRequired),
builder.ToImmutable());
}

Expand Down Expand Up @@ -303,11 +313,11 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
$$"""
/// <inheritdoc cref="{{propertyInfo.FieldName}}"/>
{{propertyAttributes}}
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}}
{
get => {{propertyInfo.FieldName}};
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("{{propertyInfo.FieldName}}")]
{{propertyInfo.AccessModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value);
{{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value);
}
""";
}
Expand All @@ -316,7 +326,7 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo)
$$"""
/// <inheritdoc cref="{{propertyInfo.FieldName}}"/>
{{propertyAttributes}}
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}}set => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); }
{{propertyInfo.TargetInfo.TargetVisibility}}{{propertyInfo.Inheritance}} {{propertyInfo.UseRequired}}{{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} { get => {{propertyInfo.FieldName}}; {{propertyInfo.AccessModifier}} => this.RaiseAndSetIfChanged(ref {{propertyInfo.FieldName}}, value); }
""";
}

Expand Down

0 comments on commit a271660

Please sign in to comment.