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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
- `[IViewFor(nameof(ViewModelName))]`
- `[RoutedControlHost("YourNameSpace.CustomControl")]`
- `[ViewModelControlHost("YourNameSpace.CustomControl")]`
- `[BindableDerivedList]` Generates a derived list from a ReadOnlyObservableCollection backing field

### Compatibility Notes
- For ReactiveUI versions **older than V19.5.31**, all `[ReactiveCommand]` options are supported except for async methods with a `CancellationToken`.
Expand Down Expand Up @@ -521,6 +522,18 @@ public partial class MyReactiveControl : UserControl
}
```

### Usage ReadOnlyObservableCollection

```csharp
using ReactiveUI.SourceGenerators;

public partial class MyReactiveClass
{
[BindableDerivedList]
private readonly ReadOnlyObservableCollection<string> _myList;
}
```

## Platform specific Attributes

### WinForms
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//HintName: ReactiveUI.SourceGenerators.BindableDerivedListAttribute.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 BindableDerivedListAttribute : Attribute;
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
</ItemGroup>

<ItemGroup>
<Folder Include="DERIVEDLIST\" />
<Folder Include="IVIEWFOR\" />
<Folder Include="OAPH\" />
<Folder Include="REACTIVECMD\" />
Expand Down
3 changes: 2 additions & 1 deletion src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ namespace ReactiveUI.SourceGenerator.Tests;
public sealed class TestHelper<T>(ITestOutputHelper testOutput) : IDisposable
where T : IIncrementalGenerator, new()
{
#pragma warning disable CS0618 // Type or member is obsolete
/// <summary>
/// Represents the NuGet library dependency for the Splat library.
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
private static readonly LibraryRange SplatLibrary =
new("Splat", VersionRange.AllStableFloating, LibraryDependencyTarget.Package);

Expand Down Expand Up @@ -90,6 +90,7 @@ public string VerifiedFilePath()
nameof(IViewForGenerator) => "IVIEWFOR",
nameof(RoutedControlHostGenerator) => "ROUTEDHOST",
nameof(ViewModelControlHostGenerator) => "CONTROLHOST",
nameof(BindableDerivedListGenerator) => "DERIVEDLIST",
_ => name,
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) 2024 .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 ReactiveUI.SourceGenerators;
using Xunit.Abstractions;

namespace ReactiveUI.SourceGenerator.Tests;

/// <summary>
/// BindableDerivedListGeneratorTests.
/// </summary>
public class BindableDerivedListGeneratorTests(ITestOutputHelper output) : TestBase<BindableDerivedListGenerator>(output)
{
/// <summary>
/// Tests that the source generator correctly generates reactive properties.
/// </summary>
/// <returns>A task to monitor the async.</returns>
[Fact]
public Task FromReactiveProperties()
{
// Arrange: Setup the source code that matches the generator input expectations.
const string sourceCode = """
using System.Collections.ObjectModel;
using DynamicData;

namespace TestNs;

public partial class TestVM
{
[BindableDerivedList]
private ReadOnlyObservableCollection<int> _test1;
}
""";

// Act: Initialize the helper and run the generator. Assert: Verify the generated code.
return TestHelper.TestPass(sourceCode);
}
}
25 changes: 25 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/Person.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) 2024 .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 ReactiveUI;
using ReactiveUI.SourceGenerators;

namespace SGReactiveUI.SourceGenerators.Test;

/// <summary>
/// Person.
/// </summary>
/// <seealso cref="ReactiveUI.ReactiveObject" />
public partial class Person : ReactiveObject
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="Person"/> is deleted.
/// </summary>
/// <value>
/// <c>true</c> if deleted; otherwise, <c>false</c>.
/// </value>
[Reactive]
public bool Deleted { get; set; }
}
17 changes: 17 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.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Reactive;
using System.Reactive.Concurrency;
Expand All @@ -11,6 +12,7 @@
using System.Reactive.Subjects;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
using DynamicData;
using ReactiveUI;
using ReactiveUI.SourceGenerators;

Expand Down Expand Up @@ -66,6 +68,12 @@ public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDis
[Reactive(SetModifier = AccessModifier.Init, UseRequired = true)]
private string _mustBeSet;

[Reactive]
private IEnumerable<Person> _people = [new Person()];

[BindableDerivedList]
private ReadOnlyObservableCollection<Person>? _visiblePeople;

/// <summary>
/// Initializes a new instance of the <see cref="TestViewModel"/> class.
/// </summary>
Expand Down Expand Up @@ -186,6 +194,15 @@ public TestViewModel()
_observableAsPropertyFromPropertyHelper = _fromPartialTestSubject.ToProperty(this, x => x.ObservableAsPropertyFromProperty);
_fromPartialTestSubject.OnNext(11);
Console.Out.WriteLine($"Observable updated, value should be 11, value is : {ObservableAsPropertyFromProperty}");

this.WhenAnyValue(vm => vm.People)
.Subscribe(people => people
.AsObservableChangeSet()
.AutoRefresh(x => x.Deleted)
.Filter(x => !x.Deleted)
.Bind(out _visiblePeople)
.Subscribe());

Console.ReadLine();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ RXUISG0015 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https:/
RXUISG0017 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
RXUISG0018 | ReactiveUI.SourceGenerators.ObservableAsPropertyFromObservableGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html
RXUISG0018 | ReactiveUI.SourceGenerators.ReactiveGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html

RXUISG0019 | ReactiveUI.SourceGenerators.BindableDerivedListGenerator | Error | See https://www.reactiveui.net/docs/handbook/view-models/boilerplate-code.html

## Rules
Shipped in ReactiveUI.SourceGenerators
Expand Down Expand Up @@ -83,3 +83,6 @@ This rule checks if the `ObservableAsProperty` has Invalid class inheritance, mu

- RXUISG0018 - ReactiveGenerator
This rule checks if the `Reactive` has Invalid class inheritance, must inherit from `ReactiveObject`.

- RXUISG0019 - BindableDerivedListGenerator
- This rule checks if the `BindableDerivedList` has Invalid property inheritance type.
30 changes: 13 additions & 17 deletions src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -435,33 +435,29 @@ internal sealed class RoutedControlHostAttribute(string? baseType) : Attribute;
#pragma warning restore
""";

public const string IsExternalInitType = "System.Runtime.CompilerServices.IsExternalInit";
public const string BindableDerivedListAttributeType = "ReactiveUI.SourceGenerators.BindableDerivedListAttribute";

public static string IsExternalInit => $$"""
public static string BindableDerivedListAttribute => $$"""
// Copyright (c) {{DateTime.Now.Year}} .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

#if !NET5_0_OR_GREATER

namespace System.Runtime.CompilerServices;
using System;

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

/// <summary>
/// Reserved to be used by the compiler for tracking metadata. This class should not be used by developers in source code.
/// ReactiveAttribute.
/// </summary>
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
static class IsExternalInit;

#endif

/// <seealso cref="Attribute" />
[global::System.CodeDom.Compiler.GeneratedCode("{{ReactiveGenerator.GeneratorName}}", "{{ReactiveGenerator.GeneratorVersion}}")]
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)]
internal sealed class BindableDerivedListAttribute : Attribute;
#nullable restore
#pragma warning restore
""";
}
Expand Down
Loading