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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

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

/// <summary>
/// IReactiveObject Attribute.
/// </summary>
/// <seealso cref="System.Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal sealed class IReactiveObjectAttribute : global::System.Attribute;
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//HintName: TestNs.TestVM.IReactiveObject.g.cs
// <auto-generated/>
#pragma warning disable
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.ComponentModel;
using ReactiveUI;

namespace TestNs
{
/// <summary>
/// Partial class for the TestVM which contains ReactiveUI IReactiveObject initialization.
/// </summary>
public partial class TestVM : IReactiveObject
{
private bool _propertyChangingEventsSubscribed;
private bool _propertyChangedEventsSubscribed;

/// <inheritdoc/>
public event PropertyChangingEventHandler? PropertyChanging
{
add
{
if (!_propertyChangingEventsSubscribed)
{
this.SubscribePropertyChangingEvents();
_propertyChangingEventsSubscribed = true;
}

PropertyChangingHandler += value;
}
remove => PropertyChangingHandler -= value;
}

/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged
{
add
{
if (!_propertyChangedEventsSubscribed)
{
this.SubscribePropertyChangedEvents();
_propertyChangedEventsSubscribed = true;
}

PropertyChangedHandler += value;
}
remove => PropertyChangedHandler -= value;
}

[SuppressMessage("Roslynator", "RCS1159:Use EventHandler<T>", Justification = "Long term design.")]
private event PropertyChangingEventHandler? PropertyChangingHandler;

[SuppressMessage("Roslynator", "RCS1159:Use EventHandler<T>", Justification = "Long term design.")]
private event PropertyChangedEventHandler? PropertyChangedHandler;

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) =>
PropertyChangingHandler?.Invoke(this, args);

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) =>
PropertyChangedHandler?.Invoke(this, args);
}
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
<Folder Include="OAPH\" />
<Folder Include="REACTIVECMD\" />
<Folder Include="REACTIVE\" />
<Folder Include="REACTIVEOBJ\" />
<Folder Include="REACTIVECOLL\" />
</ItemGroup>

Expand Down
1 change: 1 addition & 0 deletions src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public string VerifiedFilePath()
nameof(ViewModelControlHostGenerator) => "CONTROLHOST",
nameof(BindableDerivedListGenerator) => "DERIVEDLIST",
nameof(ReactiveCollectionGenerator) => "REACTIVECOLL",
nameof(ReactiveObjectGenerator) => "REACTIVEOBJ",
_ => name,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Unit tests for the Reactive generator.
/// </summary>
[TestFixture]
public class ReactiveObjectGeneratorTests : TestBase<ReactiveObjectGenerator>
{
/// <summary>
/// Tests the ReactiveObject generator with IReactiveObjectAttribute.
/// </summary>
/// <returns>A task to monitor the async.</returns>
[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);
}
}
6 changes: 3 additions & 3 deletions src/ReactiveUI.SourceGenerators.Execute/Person.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -14,7 +13,8 @@ namespace SGReactiveUI.SourceGenerators.Test;
/// </summary>
/// <seealso cref="ReactiveUI.ReactiveObject" />
[ExcludeFromCodeCoverage]
public partial class Person : ReactiveObject
[IReactiveObject]
public partial class Person
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="Person"/> is deleted.
Expand All @@ -23,5 +23,5 @@ public partial class Person : ReactiveObject
/// <c>true</c> if deleted; otherwise, <c>false</c>.
/// </value>
[Reactive]
public bool Deleted { get; set; }
public partial bool Deleted { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -89,12 +91,12 @@ internal enum SplatRegistrationType
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReactiveObjectAttribute.
/// IReactiveObject Attribute.
/// </summary>
/// <seealso cref="System.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
""";
Expand All @@ -119,7 +121,7 @@ internal sealed class ReactiveObjectAttribute : global::System.Attribute;
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// ReativeCommandAttribute.
/// ReactiveCommand Attribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "{{ReactiveGenerator.GeneratorVersion}}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System.Globalization;
using Microsoft.CodeAnalysis;
using ReactiveUI.SourceGenerators.Helpers;

namespace ReactiveUI.SourceGenerators.Extensions;

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// A source generator for generating reative properties.
/// A source generator for generating reactive properties.
/// </summary>
[Generator(LanguageNames.CSharp)]
public sealed partial class IViewForGenerator : IIncrementalGenerator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// A source generator for generating reative properties.
/// A source generator for generating reactive properties.
/// </summary>
public sealed partial class ObservableAsPropertyGenerator
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace ReactiveUI.SourceGenerators;

/// <summary>
/// A source generator for generating reative properties.
/// A source generator for generating reactive properties.
/// </summary>
public sealed partial class ObservableAsPropertyGenerator
{
Expand Down
Loading
Loading