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
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This documentation covers using ReactiveUI Source Generators to simplify and enh

ReactiveUI Source Generators automatically generate ReactiveUI objects to streamline your code. These Source Generators are designed to work with ReactiveUI V19.5.31+ and support the following features:

- `[Reactive]`
- `[Reactive]` With field and access modifiers, partial property support (C# 13 Visual Studio Version 17.12.0)
- `[ObservableAsProperty]`
- `[ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]`
- `[ReactiveCommand]`
Expand Down Expand Up @@ -131,6 +131,23 @@ public partial class MyReactiveClass : ReactiveObject
}
```

### Usage Reactive property from partial property

Partial properties are supported in C# 13 and Visual Studio 17.12.0 and later.
Both the getter and setter must be empty, and the `[Reactive]` attribute must be placed on the property.
Override and Virtual properties are supported.
Set Access Modifier is also supported on partial properties.

```csharp
using ReactiveUI.SourceGenerators;

public partial class MyReactiveClass : ReactiveObject
{
[Reactive]
public partial string MyProperty { get; set; }
}
```

## Usage ObservableAsPropertyHelper `[ObservableAsProperty]`

ObservableAsPropertyHelper is used to create a read-only property from an IObservable. The generated code will create a backing field and a property that returns the value of the backing field. The backing field is initialized with the value of the IObservable when the class is instantiated.
Expand Down
4 changes: 2 additions & 2 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<PackageVersion Include="xunit.runner.console" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.1" />
<PackageVersion Include="Xunit.StaFact" Version="1.2.69" />
<PackageVersion Include="FluentAssertions" Version="8.0.0" />
<PackageVersion Include="FluentAssertions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Reactive.Testing" Version="6.0.1" />
<PackageVersion Include="PublicApiGenerator" Version="11.3.0" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.3" />
Expand All @@ -45,4 +45,4 @@
<PackageVersion Include="Basic.Reference.Assemblies.Net80" Version="1.8.0" />
<PackageVersion Include="Basic.Reference.Assemblies.Net80Windows" Version="1.8.0" />
</ItemGroup>
</Project>
</Project>
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 | AttributeTargets.Property, 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
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestNs
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_test3"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Runtime.Serialization.DataMemberAttribute()]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestNs
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_test1"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Test1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestNs
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_test2"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int Test2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ namespace TestNs
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Runtime.Serialization.DataMemberAttribute()]
[global::System.Text.Json.Serialization.JsonIncludeAttribute()]
public string? Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
public string? Name
{
get => _name;
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_name")]
set => this.RaiseAndSetIfChanged(ref _name, value);
}
}
}
#nullable restore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ namespace TestNs1
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Runtime.Serialization.DataMemberAttribute()]
[global::System.Text.Json.Serialization.JsonIncludeAttribute()]
public string? Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
public string? Name
{
get => _name;
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_name")]
set => this.RaiseAndSetIfChanged(ref _name, value);
}
}
}
#nullable restore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@ namespace TestNs2
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_name"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
[global::System.Runtime.Serialization.DataMemberAttribute()]
[global::System.Text.Json.Serialization.JsonIncludeAttribute()]
public string? Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); }
public string? Name
{
get => _name;
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_name")]
set => this.RaiseAndSetIfChanged(ref _name, value);
}
}
}
#nullable restore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestNs
/// </summary>
public partial class TestVM
{

/// <inheritdoc cref="_mustBeSet"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public string MustBeSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace ReactiveUI.SourceGenerators;
/// ReactiveAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveAttribute : Attribute
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public partial class TestViewModel3
/// </summary>
public partial class TestInnerClass1
{

/// <inheritdoc cref="_testInner1"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner1
Expand All @@ -22,6 +23,7 @@ public int TestInner1
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_testInner1")]
set => this.RaiseAndSetIfChanged(ref _testInner1, value);
}

/// <inheritdoc cref="_testInner11"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public partial class TestInnerClass2
/// </summary>
public partial class TestInnerClass3
{

/// <inheritdoc cref="_testInner3"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner3
Expand All @@ -24,6 +25,7 @@ public int TestInner3
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_testInner3")]
set => this.RaiseAndSetIfChanged(ref _testInner3, value);
}

/// <inheritdoc cref="_testInner33"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner33
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public partial class TestViewModel3
/// </summary>
public partial class TestInnerClass2
{

/// <inheritdoc cref="_testInner2"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner2
Expand All @@ -22,6 +23,7 @@ public int TestInner2
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_testInner2")]
set => this.RaiseAndSetIfChanged(ref _testInner2, value);
}

/// <inheritdoc cref="_testInner22"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public int TestInner22
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace TestNs1
/// </summary>
public partial class TestViewModel3
{

/// <inheritdoc cref="_testVM3Property"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public float TestVM3Property
Expand All @@ -20,6 +21,7 @@ public float TestVM3Property
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("_testVM3Property")]
set => this.RaiseAndSetIfChanged(ref _testVM3Property, value);
}

/// <inheritdoc cref="_testVM3Property2"/>
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public float TestVM3Property2
Expand Down
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>
<LangVersion>13.0</LangVersion>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<NoWarn>$(NoWarn);CA1812;CA1001</NoWarn>
Expand Down Expand Up @@ -42,7 +42,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ReactiveUI.SourceGenerators\ReactiveUI.SourceGenerators.csproj" />
<ProjectReference Include="..\ReactiveUI.SourceGenerators.Roslyn4120\ReactiveUI.SourceGenerators.Roslyn4120.csproj" />
</ItemGroup>

</Project>
8 changes: 6 additions & 2 deletions src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task InitializeAsync()

// Download necessary NuGet package files.
var inputGroup = await NuGetPackageHelper.DownloadPackageFilesAndFolder(
new[] { SplatLibrary, ReactiveuiLibrary },
[SplatLibrary, ReactiveuiLibrary],
targetFrameworks,
packageOutputDirectory: null).ConfigureAwait(false);

Expand All @@ -127,7 +127,11 @@ public void TestFail(

var utility = new SourceGeneratorUtility(x => testOutput.WriteLine(x));

#pragma warning disable IDE0053 // Use expression body for lambda expression
#pragma warning disable RCS1021 // Convert lambda expression body to expression body
Assert.Throws<InvalidOperationException>(() => { RunGeneratorAndCheck(source); });
#pragma warning restore RCS1021 // Convert lambda expression body to expression body
#pragma warning restore IDE0053 // Use expression body for lambda expression
}

/// <summary>
Expand Down Expand Up @@ -183,7 +187,7 @@ public SettingsTask RunGeneratorAndCheck(
.Concat(_eventCompiler.ReferencedModules.Select(x => MetadataReference.CreateFromFile(x.PEFile!.FileName)))
.Concat(_eventCompiler.NeededModules.Select(x => MetadataReference.CreateFromFile(x.PEFile!.FileName))));

var syntaxTree = CSharpSyntaxTree.ParseText(code, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12));
var syntaxTree = CSharpSyntaxTree.ParseText(code, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp13));

// Create a compilation with the provided source code.
var compilation = CSharpCompilation.Create(
Expand Down
Loading