diff --git a/README.md b/README.md
index 4d9ecdb..6d5a239 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,7 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
- `[ViewModelControlHost("YourNameSpace.CustomControl")]`
- `[BindableDerivedList]` Generates a derived list from a ReadOnlyObservableCollection backing field
- `[ReactiveCollection]` Generates property changed notifications on add, remove, new actions on a ObservableCollection backing field
+- `[IReactiveObject]` Generates IReactiveObject implementation for classes not able to inherit from ReactiveObject
#### IViewFor Registration generator
@@ -684,6 +685,19 @@ public partial class MyReactiveClass : ReactiveObject
}
```
+### ReactiveObject implementation for classes not able to inherit from ReactiveObject
+```csharp
+using ReactiveUI;
+using ReactiveUI.SourceGenerators;
+
+[IReactiveObject]
+public partial class MyReactiveClass
+{
+ [Reactive]
+ private string _myProperty;
+}
+```
+
### TODO:
- Add ObservableAsProperty to generate from a IObservable method with parameters.
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithAccessModifier#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithAccessModifier#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithAccessModifier#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithAccessModifier#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNestedClasses#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithOutputScheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
index 1462b40..bcdbe60 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithParameter#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs
@@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#ReactiveUI.SourceGenerators.IReactiveObjectAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#ReactiveUI.SourceGenerators.IReactiveObjectAttribute.g.verified.cs
new file mode 100644
index 0000000..3a7be2f
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#ReactiveUI.SourceGenerators.IReactiveObjectAttribute.g.verified.cs
@@ -0,0 +1,19 @@
+//HintName: ReactiveUI.SourceGenerators.IReactiveObjectAttribute.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.
+
+//
+#pragma warning disable
+#nullable enable
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// IReactiveObject Attribute.
+///
+///
+[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
+internal sealed class IReactiveObjectAttribute : global::System.Attribute;
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#TestNs.TestVM.IReactiveObject.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#TestNs.TestVM.IReactiveObject.g.verified.cs
new file mode 100644
index 0000000..cb482d4
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVEOBJ/ReactiveObjectGeneratorTests.FromReactiveObject#TestNs.TestVM.IReactiveObject.g.verified.cs
@@ -0,0 +1,68 @@
+//HintName: TestNs.TestVM.IReactiveObject.g.cs
+//
+#pragma warning disable
+#nullable enable
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ComponentModel;
+using ReactiveUI;
+
+namespace TestNs
+{
+ ///
+ /// Partial class for the TestVM which contains ReactiveUI IReactiveObject initialization.
+ ///
+ public partial class TestVM : IReactiveObject
+ {
+ private bool _propertyChangingEventsSubscribed;
+ private bool _propertyChangedEventsSubscribed;
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging
+ {
+ add
+ {
+ if (!_propertyChangingEventsSubscribed)
+ {
+ this.SubscribePropertyChangingEvents();
+ _propertyChangingEventsSubscribed = true;
+ }
+
+ PropertyChangingHandler += value;
+ }
+ remove => PropertyChangingHandler -= value;
+ }
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged
+ {
+ add
+ {
+ if (!_propertyChangedEventsSubscribed)
+ {
+ this.SubscribePropertyChangedEvents();
+ _propertyChangedEventsSubscribed = true;
+ }
+
+ PropertyChangedHandler += value;
+ }
+ remove => PropertyChangedHandler -= value;
+ }
+
+ [SuppressMessage("Roslynator", "RCS1159:Use EventHandler", Justification = "Long term design.")]
+ private event PropertyChangingEventHandler? PropertyChangingHandler;
+
+ [SuppressMessage("Roslynator", "RCS1159:Use EventHandler", Justification = "Long term design.")]
+ private event PropertyChangedEventHandler? PropertyChangedHandler;
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) =>
+ PropertyChangingHandler?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) =>
+ PropertyChangedHandler?.Invoke(this, args);
+ }
+}
+#nullable restore
+#pragma warning restore
\ No newline at end of file
diff --git a/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj b/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj
index 8630fcf..f89d767 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj
+++ b/src/ReactiveUI.SourceGenerator.Tests/ReactiveUI.SourceGenerators.Tests.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
index 8309201..6f471a3 100644
--- a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
+++ b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
@@ -87,6 +87,7 @@ public string VerifiedFilePath()
nameof(ViewModelControlHostGenerator) => "CONTROLHOST",
nameof(BindableDerivedListGenerator) => "DERIVEDLIST",
nameof(ReactiveCollectionGenerator) => "REACTIVECOLL",
+ nameof(ReactiveObjectGenerator) => "REACTIVEOBJ",
_ => name,
};
}
diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveObjectGeneratorTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveObjectGeneratorTests.cs
new file mode 100644
index 0000000..fde4731
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveObjectGeneratorTests.cs
@@ -0,0 +1,41 @@
+// 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.SourceGenerators;
+
+namespace ReactiveUI.SourceGenerator.Tests;
+
+///
+/// Unit tests for the Reactive generator.
+///
+[TestFixture]
+public class ReactiveObjectGeneratorTests : TestBase
+{
+ ///
+ /// Tests the ReactiveObject generator with IReactiveObjectAttribute.
+ ///
+ /// A task to monitor the async.
+ [Test]
+ public Task FromReactiveObject()
+ {
+ // Arrange: Setup the source code that matches the generator input expectations.
+ const string sourceCode = """
+ using System;
+ using ReactiveUI.SourceGenerators;
+ using System.Reactive.Linq;
+ namespace TestNs;
+
+ [IReactiveObject]
+ public partial class TestVM
+ {
+ [Reactive]
+ private int _test1 = 10;
+ }
+ """;
+
+ // Act: Initialize the helper and run the generator. Assert: Verify the generated code.
+ return TestHelper.TestPass(sourceCode);
+ }
+}
diff --git a/src/ReactiveUI.SourceGenerators.Execute/Person.cs b/src/ReactiveUI.SourceGenerators.Execute/Person.cs
index 2c05550..1958f51 100644
--- a/src/ReactiveUI.SourceGenerators.Execute/Person.cs
+++ b/src/ReactiveUI.SourceGenerators.Execute/Person.cs
@@ -4,7 +4,6 @@
// See the LICENSE file in the project root for full license information.
using System.Diagnostics.CodeAnalysis;
-using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace SGReactiveUI.SourceGenerators.Test;
@@ -14,7 +13,8 @@ namespace SGReactiveUI.SourceGenerators.Test;
///
///
[ExcludeFromCodeCoverage]
-public partial class Person : ReactiveObject
+[IReactiveObject]
+public partial class Person
{
///
/// Gets or sets a value indicating whether this is deleted.
@@ -23,5 +23,5 @@ public partial class Person : ReactiveObject
/// true if deleted; otherwise, false.
///
[Reactive]
- public bool Deleted { get; set; }
+ public partial bool Deleted { get; set; }
}
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs
index ccecb84..2cdfdd8 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs
@@ -77,6 +77,8 @@ internal enum SplatRegistrationType
#pragma warning restore
""";
+ public const string ReactiveObjectAttributeType = "ReactiveUI.SourceGenerators.IReactiveObjectAttribute";
+
public static string ReactiveObjectAttribute => $$"""
// Copyright (c) {{DateTime.Now.Year}} .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
@@ -89,12 +91,12 @@ internal enum SplatRegistrationType
namespace ReactiveUI.SourceGenerators;
///
-/// ReactiveObjectAttribute.
+/// IReactiveObject Attribute.
///
///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveObjectGenerator", "{{ReactiveGenerator.GeneratorVersion}}")]
[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
-internal sealed class ReactiveObjectAttribute : global::System.Attribute;
+internal sealed class IReactiveObjectAttribute : global::System.Attribute;
#nullable restore
#pragma warning restore
""";
@@ -119,7 +121,7 @@ internal sealed class ReactiveObjectAttribute : global::System.Attribute;
namespace ReactiveUI.SourceGenerators;
///
-/// ReativeCommandAttribute.
+/// ReactiveCommand Attribute.
///
///
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "{{ReactiveGenerator.GeneratorVersion}}")]
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/BindableDerivedList/BindableDerivedListGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/BindableDerivedList/BindableDerivedListGenerator.cs
index fdf8b79..51f9e2f 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/BindableDerivedList/BindableDerivedListGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/BindableDerivedList/BindableDerivedListGenerator.cs
@@ -3,8 +3,6 @@
// 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 System;
-using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/FieldSyntaxExtensions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/FieldSyntaxExtensions.cs
index fb62a63..067883a 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/FieldSyntaxExtensions.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/Core/Extensions/FieldSyntaxExtensions.cs
@@ -5,6 +5,7 @@
using System.Globalization;
using Microsoft.CodeAnalysis;
+using ReactiveUI.SourceGenerators.Helpers;
namespace ReactiveUI.SourceGenerators.Extensions;
@@ -124,7 +125,7 @@ internal static bool IsTargetTypeValid(this IFieldSymbol fieldSymbol)
{
var isObservableObject = fieldSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
var isIObservableObject = fieldSymbol.ContainingType.ImplementsFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
- var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
+ var hasObservableObjectAttribute = fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveObjectAttributeType);
return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
}
@@ -138,7 +139,7 @@ internal static bool IsTargetTypeValid(this IPropertySymbol propertySymbol)
{
var isObservableObject = propertySymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
var isIObservableObject = propertySymbol.ContainingType.ImplementsFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
- var hasObservableObjectAttribute = propertySymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
+ var hasObservableObjectAttribute = propertySymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveObjectAttributeType);
return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
}
@@ -152,7 +153,7 @@ internal static bool IsTargetTypeValid(this IMethodSymbol methodSymbol)
{
var isObservableObject = methodSymbol.ContainingType.InheritsFromFullyQualifiedMetadataName("ReactiveUI.ReactiveObject");
var isIObservableObject = methodSymbol.ContainingType.ImplementsFullyQualifiedMetadataName("ReactiveUI.IReactiveObject");
- var hasObservableObjectAttribute = methodSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName("ReactiveUI.SourceGenerators.ReactiveObjectAttribute");
+ var hasObservableObjectAttribute = methodSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedMetadataName(AttributeDefinitions.ReactiveObjectAttributeType);
return isIObservableObject || isObservableObject || hasObservableObjectAttribute;
}
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs
index 700bfef..d55ee07 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs
@@ -26,8 +26,6 @@ public partial class IViewForGenerator
internal static readonly string GeneratorName = typeof(IViewForGenerator).FullName!;
internal static readonly string GeneratorVersion = typeof(IViewForGenerator).Assembly.GetName().Version.ToString();
- private static readonly string[] excludeFromCodeCoverage = ["[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"];
-
private static IViewForInfo? GetClassInfo(in GenericGeneratorAttributeSyntaxContext context, CancellationToken token)
{
if (!(context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword)))
@@ -126,7 +124,7 @@ public partial class IViewForGenerator
private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, IViewForInfo iviewForInfo)
{
// Prepare any forwarded property attributes
- var forwardedAttributesString = string.Join("\n ", excludeFromCodeCoverage);
+ var forwardedAttributesString = string.Join("\n ", AttributeDefinitions.ExcludeFromCodeCoverage);
switch (iviewForInfo.BaseType)
{
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs
index fef7d26..1bb994e 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class IViewForGenerator : IIncrementalGenerator
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs
index ed6ee33..838816d 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromField}.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
public sealed partial class ObservableAsPropertyGenerator
{
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
index 038b534..3f100d8 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ObservableAsProperty/ObservableAsPropertyGenerator{FromObservable}.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
public sealed partial class ObservableAsPropertyGenerator
{
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.cs
index c9f7fa5..f4469c4 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class ReactiveGenerator : IIncrementalGenerator
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCollection/ReactiveCollectionGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCollection/ReactiveCollectionGenerator.cs
index 5114905..6d717ac 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCollection/ReactiveCollectionGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCollection/ReactiveCollectionGenerator.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class ReactiveCollectionGenerator : IIncrementalGenerator
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs
index e40aef2..e2f3822 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class ReactiveCommandGenerator : IIncrementalGenerator
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/Models/ReactiveObjectInfo.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/Models/ReactiveObjectInfo.cs
new file mode 100644
index 0000000..8181866
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/Models/ReactiveObjectInfo.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace ReactiveUI.SourceGenerators.Models;
+
+///
+/// A model with gathered information about a generated ReactiveObject (view model).
+///
+internal sealed record ReactiveObjectInfo(
+ TargetInfo TargetInfo);
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.Execute.cs
new file mode 100644
index 0000000..65d076d
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.Execute.cs
@@ -0,0 +1,119 @@
+// 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 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;
+using ReactiveUI.SourceGenerators.Models;
+
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// A source generator for generating reactiveObject properties.
+///
+public partial class ReactiveObjectGenerator
+{
+ internal static readonly string GeneratorName = typeof(ReactiveObjectGenerator).FullName!;
+ internal static readonly string GeneratorVersion = typeof(ReactiveObjectGenerator).Assembly.GetName().Version.ToString();
+
+ private static ReactiveObjectInfo? GetClassInfo(in GenericGeneratorAttributeSyntaxContext context, CancellationToken token)
+ {
+ if (!(context.TargetNode is ClassDeclarationSyntax declaredClass && declaredClass.Modifiers.Any(SyntaxKind.PartialKeyword)))
+ {
+ return default;
+ }
+
+ var symbol = context.TargetSymbol;
+ token.ThrowIfCancellationRequested();
+
+ if (symbol is not INamedTypeSymbol classSymbol)
+ {
+ return default;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Get the containing type info
+ var targetInfo = TargetInfo.From(classSymbol);
+
+ token.ThrowIfCancellationRequested();
+ return new(targetInfo);
+ }
+
+ private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, ReactiveObjectInfo reactiveObjectInfo) =>
+ $$"""
+//
+#pragma warning disable
+#nullable enable
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.ComponentModel;
+using ReactiveUI;
+
+namespace {{containingNamespace}}
+{
+ ///
+ /// Partial class for the {{containingTypeName}} which contains ReactiveUI IReactiveObject initialization.
+ ///
+ {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} : IReactiveObject
+ {
+ private bool _propertyChangingEventsSubscribed;
+ private bool _propertyChangedEventsSubscribed;
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging
+ {
+ add
+ {
+ if (!_propertyChangingEventsSubscribed)
+ {
+ this.SubscribePropertyChangingEvents();
+ _propertyChangingEventsSubscribed = true;
+ }
+
+ PropertyChangingHandler += value;
+ }
+ remove => PropertyChangingHandler -= value;
+ }
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged
+ {
+ add
+ {
+ if (!_propertyChangedEventsSubscribed)
+ {
+ this.SubscribePropertyChangedEvents();
+ _propertyChangedEventsSubscribed = true;
+ }
+
+ PropertyChangedHandler += value;
+ }
+ remove => PropertyChangedHandler -= value;
+ }
+
+ [SuppressMessage("Roslynator", "RCS1159:Use EventHandler", Justification = "Long term design.")]
+ private event PropertyChangingEventHandler? PropertyChangingHandler;
+
+ [SuppressMessage("Roslynator", "RCS1159:Use EventHandler", Justification = "Long term design.")]
+ private event PropertyChangedEventHandler? PropertyChangedHandler;
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) =>
+ PropertyChangingHandler?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) =>
+ PropertyChangedHandler?.Invoke(this, args);
+ }
+}
+#nullable restore
+#pragma warning restore
+""";
+}
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.cs
new file mode 100644
index 0000000..afba4e5
--- /dev/null
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveObject/ReactiveObjectGenerator.cs
@@ -0,0 +1,61 @@
+// 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 System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using ReactiveUI.SourceGenerators.Helpers;
+
+namespace ReactiveUI.SourceGenerators;
+
+///
+/// A source generator for generating reactive properties.
+///
+[Generator(LanguageNames.CSharp)]
+public sealed partial class ReactiveObjectGenerator : IIncrementalGenerator
+{
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ context.RegisterPostInitializationOutput(ctx =>
+ ctx.AddSource(AttributeDefinitions.ReactiveObjectAttributeType + ".g.cs", SourceText.From(AttributeDefinitions.ReactiveObjectAttribute, Encoding.UTF8)));
+
+ // Gather info for all annotated IReactiveObject Classes
+ var reactiveObjectInfo =
+ context.SyntaxProvider
+ .ForAttributeWithMetadataNameWithGenerics(
+ AttributeDefinitions.ReactiveObjectAttributeType,
+ static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
+ static (context, token) => GetClassInfo(context, token))
+ .Where(x => x != null)
+ .Select((x, _) => x!)
+ .Collect();
+
+ // Generate the requested properties and methods for IReactiveObject
+ context.RegisterSourceOutput(reactiveObjectInfo, static (context, input) =>
+ {
+ var groupedPropertyInfo = input.GroupBy(
+ static info => (info.TargetInfo.FileHintName, info.TargetInfo.TargetName, info.TargetInfo.TargetNamespace, info.TargetInfo.TargetVisibility, info.TargetInfo.TargetType),
+ static info => info)
+ .ToImmutableArray();
+
+ foreach (var grouping in groupedPropertyInfo)
+ {
+ var items = grouping.ToImmutableArray();
+
+ if (items.Length == 0)
+ {
+ continue;
+ }
+
+ var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, grouping.FirstOrDefault());
+ context.AddSource(grouping.Key.FileHintName + ".IReactiveObject.g.cs", source);
+ }
+ });
+ }
+}
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/RoutedControlHost/RoutedControlHostGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/RoutedControlHost/RoutedControlHostGenerator.cs
index 5fe349e..9ea0bf6 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/RoutedControlHost/RoutedControlHostGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/RoutedControlHost/RoutedControlHostGenerator.cs
@@ -15,7 +15,7 @@
namespace ReactiveUI.SourceGenerators.WinForms;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class RoutedControlHostGenerator : IIncrementalGenerator
diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ViewModelControlHost/ViewModelControlHostGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ViewModelControlHost/ViewModelControlHostGenerator.cs
index 3984534..d0922b4 100644
--- a/src/ReactiveUI.SourceGenerators.Roslyn/ViewModelControlHost/ViewModelControlHostGenerator.cs
+++ b/src/ReactiveUI.SourceGenerators.Roslyn/ViewModelControlHost/ViewModelControlHostGenerator.cs
@@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators.WinForms;
///
-/// A source generator for generating reative properties.
+/// A source generator for generating reactive properties.
///
[Generator(LanguageNames.CSharp)]
public sealed partial class ViewModelControlHostGenerator : IIncrementalGenerator