From 462fe4ba2cad6a3d0caad30dbd3a321b175ee18e Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Sun, 7 Dec 2025 23:46:45 +0000 Subject: [PATCH 1/8] Add ViewModelRegistrationType to IViewFor attribute Introduces ViewModelRegistrationType to the IViewFor attribute and updates generator logic to support separate Splat registration for view models. Documentation, tests, and source generator code are updated to reflect the new option and ensure correct registration behavior. --- README.md | 4 +- ...Generators.IViewForAttribute.g.verified.cs | 16 ++++++- .../TestHelper.cs | 6 +-- .../TestViewWpf.cs | 2 +- .../AttributeDefinitions.cs | 32 +++++++++----- .../IViewFor/IViewForGenerator.Execute.cs | 44 ++++++++++++++++++- .../IViewFor/Models/IViewForInfo.cs | 4 +- 7 files changed, 86 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 15a14c7..d462957 100644 --- a/README.md +++ b/README.md @@ -560,7 +560,7 @@ The class must inherit from a UI Control from any of the following platforms and ### IViewFor with Splat Registration Type -Choose from the following Splat Registration Types: +Choose from the following Splat Registration Types, option for IViewFor Registration and / or ViewModel Registration: - `SplatRegistrationType.PerRequest` - `SplatRegistrationType.LazySingleton` - `SplatRegistrationType.Constant` @@ -569,7 +569,7 @@ Choose from the following Splat Registration Types: ```csharp using ReactiveUI.SourceGenerators; using Splat; -[IViewFor(RegistrationType = SplatRegistrationType.PerRequest)] +[IViewFor(RegistrationType = SplatRegistrationType.PerRequest, ViewModelRegistrationType = SplatRegistrationType.LazySingleton)] public partial class MyReactiveControl : UserControl { public MyReactiveControl() diff --git a/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs index 11d67f6..85c179c 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs @@ -21,10 +21,16 @@ namespace ReactiveUI.SourceGenerators; internal sealed class IViewForAttribute : global::System.Attribute { /// - /// Gets the Splat registration type for Splat registration. + /// Gets the Splat registration type for Splat IViewFor registration. /// Registers IViewFor in the Splat service locator. /// public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; + + /// + /// Gets the Splat registration type for Splat View Model registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } /// @@ -39,10 +45,16 @@ internal sealed class IViewForAttribute : global::System.Attribute internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute { /// - /// Gets the Splat registration type for Splat registration. + /// Gets the Splat registration type for Splat IViewFor registration. /// Registers IViewFor in the Splat service locator. /// public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; + + /// + /// Gets the Splat registration type for Splat View Model registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs index 1c095c1..8309201 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs @@ -30,19 +30,17 @@ namespace ReactiveUI.SourceGenerator.Tests; public sealed class TestHelper : IDisposable where T : IIncrementalGenerator, new() { -#pragma warning disable CS0618 // Type or member is obsolete /// /// Represents the NuGet library dependency for the Splat library. /// private static readonly LibraryRange SplatLibrary = - new("Splat", VersionRange.AllStableFloating, LibraryDependencyTarget.Package); + new("Splat", VersionRange.AllStable, LibraryDependencyTarget.Package); /// /// Represents the NuGet library dependency for the ReactiveUI library. /// private static readonly LibraryRange ReactiveuiLibrary = - new("ReactiveUI", VersionRange.AllStableFloating, LibraryDependencyTarget.Package); -#pragma warning restore CS0618 // Type or member is obsolete + new("ReactiveUI", VersionRange.AllStable, LibraryDependencyTarget.Package); private static readonly string mscorlibPath = Path.Combine( System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs index 365e338..b855746 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs @@ -13,7 +13,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestView. /// -[IViewFor(RegistrationType = SplatRegistrationType.PerRequest)] +[IViewFor(RegistrationType = SplatRegistrationType.PerRequest, ViewModelRegistrationType = SplatRegistrationType.Constant)] public partial class TestViewWpf : Window { /// diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs index 13d2207..ad1cef8 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs @@ -79,7 +79,7 @@ namespace ReactiveUI.SourceGenerators; /// ReactiveObjectAttribute. /// /// -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveObjectGenerator", "1.1.0.0")] +[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; #nullable restore @@ -109,7 +109,7 @@ namespace ReactiveUI.SourceGenerators; /// ReativeCommandAttribute. /// /// -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveCommandGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class ReactiveCommandAttribute : global::System.Attribute { @@ -242,7 +242,7 @@ namespace ReactiveUI.SourceGenerators; /// ObservableAsPropertyAttribute. /// /// -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property | global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class ObservableAsPropertyAttribute : global::System.Attribute { @@ -297,7 +297,7 @@ namespace ReactiveUI.SourceGenerators; /// ObservableAsPropertyAttribute. /// /// -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ObservableAsPropertyGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property | global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class ObservableAsPropertyAttribute : global::System.Attribute { @@ -350,15 +350,21 @@ namespace ReactiveUI.SourceGenerators; /// Initializes a new instance of the class. /// /// Type of the view model. -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class IViewForAttribute : global::System.Attribute { /// - /// Gets the Splat registration type for Splat registration. + /// Gets the Splat registration type for Splat IViewFor registration. /// Registers IViewFor in the Splat service locator. /// public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; + + /// + /// Gets the Splat registration type for Splat View Model registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } /// @@ -369,15 +375,21 @@ internal sealed class IViewForAttribute : global::System.Attribute /// Initializes a new instance of the class. /// /// Type of the view model, ensure to use the full type name including namespace. -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute { /// - /// Gets the Splat registration type for Splat registration. + /// Gets the Splat registration type for Splat IViewFor registration. /// Registers IViewFor in the Splat service locator. /// public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; + + /// + /// Gets the Splat registration type for Splat View Model registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } #nullable restore #pragma warning restore @@ -404,7 +416,7 @@ namespace ReactiveUI.SourceGenerators.WinForms; /// Initializes a new instance of the class. /// /// Type of the view model. -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ViewModelControlHostGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ViewModelControlHostGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class ViewModelControlHostAttribute(string? baseType) : global::System.Attribute; #nullable restore @@ -432,7 +444,7 @@ namespace ReactiveUI.SourceGenerators.WinForms; /// Initializes a new instance of the class. /// /// Type of the view model. -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.RoutedControlHostGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.RoutedControlHostGenerator", "{{ReactiveGenerator.GeneratorVersion}}")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class RoutedControlHostAttribute(string? baseType) : global::System.Attribute; #nullable restore diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs index 5c061fc..fbb0525 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs @@ -103,11 +103,24 @@ public partial class IViewForGenerator _ => string.Empty, }; + token.ThrowIfCancellationRequested(); + + // Get RegistrationType enum value from the attribute + attributeData.TryGetNamedArgument("ViewModelRegistrationType", out int splatViewModelRegistrationType); + var viewModelRegistrationType = splatViewModelRegistrationType switch + { + 1 => "RegisterLazySingleton", + 2 => "RegisterConstant", + 3 => "Register", + _ => string.Empty, + }; + return new( targetInfo, viewModelTypeName!, viewForBaseType, - registrationType); + registrationType, + viewModelRegistrationType); } private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, IViewForInfo iviewForInfo) @@ -322,6 +335,12 @@ private static string GenerateRegistrationExtensions(in ImmutableArray g.First()) .ToImmutableArray(); + var viewModelRegistrations = iviewForInfo + .Where(static x => !string.IsNullOrWhiteSpace(x.SplatViewModelRegistrationType)) + .GroupBy(static x => (x.TargetInfo.TargetNamespaceWithNamespace, x.ViewModelTypeName, x.SplatRegistrationType)) + .Select(static g => g.First()) + .ToImmutableArray(); + var sb = new StringBuilder(); sb.AppendLine("if (resolver is null) throw new global::System.ArgumentNullException(nameof(resolver));"); foreach (var item in registrations) @@ -350,6 +369,29 @@ private static string GenerateRegistrationExtensions(in ImmutableArray(); + switch (item.SplatViewModelRegistrationType) + { + case "RegisterLazySingleton": + sb.AppendLine($" resolver.{item.SplatViewModelRegistrationType}<{vmType}>(() => new {vmType}());"); + break; + case "Register": + sb.AppendLine($" resolver.{item.SplatViewModelRegistrationType}<{vmType}, {vmType}>();"); + break; + case "RegisterConstant": + sb.AppendLine($" resolver.{item.SplatViewModelRegistrationType}<{vmType}>(new {vmType}());"); + break; + } + } + var registrationsBody = sb.ToString().TrimEnd(); return $$""" diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs index 36a5485..57c2042 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs @@ -3,7 +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 ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Models; namespace ReactiveUI.SourceGenerators.Input.Models; @@ -15,4 +14,5 @@ internal sealed record IViewForInfo( TargetInfo TargetInfo, string ViewModelTypeName, IViewForBaseType BaseType, - string SplatRegistrationType); + string SplatRegistrationType, + string SplatViewModelRegistrationType); From 1c3c7a5bb23840b797b9dfd1357a02e3ce4dbb4c Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 00:24:37 +0000 Subject: [PATCH 2/8] Add ExcludeFromCodeCoverage to test classes Added [ExcludeFromCodeCoverage] attribute to various test and sample classes across the project to exclude them from code coverage analysis. This helps ensure that code coverage metrics focus on production code rather than test scaffolding. --- src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute.Maui/TestViewModel.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs | 2 ++ .../InternalTestViewModel.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/Person.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/Program.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestAttribute.cs | 3 +++ src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs | 1 + src/ReactiveUI.SourceGenerators.Execute/TestViewModel2.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestViewModel3.cs | 2 ++ .../TestViewModel{partTwo}.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs | 2 ++ src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs | 2 ++ 18 files changed, 36 insertions(+) diff --git a/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs b/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs index 8cfff3f..50e3f47 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI.SourceGenerators; namespace SGReactiveUI.SourceGenerators.Test.Maui @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test.Maui /// IViewForTest. /// /// + [ExcludeFromCodeCoverage] [IViewFor] public partial class IViewForTest : Shell; } diff --git a/src/ReactiveUI.SourceGenerators.Execute.Maui/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute.Maui/TestViewModel.cs index ba2f0e1..1e6f270 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Maui/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Maui/TestViewModel.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using System.Reactive; using System.Reactive.Linq; using System.Runtime.Serialization; @@ -15,6 +16,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestClass. /// +[ExcludeFromCodeCoverage] [DataContract] public partial class TestViewModel : ReactiveObject { diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs index 6947427..21579cc 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Nested1/Class1.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Execute.Nested1; /// /// Class1. /// +[ExcludeFromCodeCoverage] public partial class Class1 : ReactiveObject { [Reactive] diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs index d904311..d5b7437 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Nested2/Class1.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Execute.Nested2; /// /// Class1. /// +[ExcludeFromCodeCoverage] public partial class Class1 : ReactiveObject { [Reactive] diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs index 906c9b5..9f7f0e4 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Execute.Nested3; /// /// Class1. /// +[ExcludeFromCodeCoverage] public partial class Class1 : ReactiveObject { [Reactive] diff --git a/src/ReactiveUI.SourceGenerators.Execute/InternalTestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/InternalTestViewModel.cs index 2a179e6..43a022f 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/InternalTestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/InternalTestViewModel.cs @@ -4,11 +4,13 @@ // See the LICENSE file in the project root for full license information. using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; namespace SGReactiveUI.SourceGenerators.Test; +[ExcludeFromCodeCoverage] internal partial class InternalTestViewModel : ReactiveObject { [ReactiveCollection] diff --git a/src/ReactiveUI.SourceGenerators.Execute/Person.cs b/src/ReactiveUI.SourceGenerators.Execute/Person.cs index 0e00c6e..2c05550 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/Person.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/Person.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -12,6 +13,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// Person. /// /// +[ExcludeFromCodeCoverage] public partial class Person : ReactiveObject { /// diff --git a/src/ReactiveUI.SourceGenerators.Execute/Program.cs b/src/ReactiveUI.SourceGenerators.Execute/Program.cs index 6db2c3a..1a4a8ab 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/Program.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/Program.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI.SourceGenerators; using Splat; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// EntryPoint. /// +[ExcludeFromCodeCoverage] public static class Program { /// diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestAttribute.cs b/src/ReactiveUI.SourceGenerators.Execute/TestAttribute.cs index 9207d0a..685aba0 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestAttribute.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestAttribute.cs @@ -3,12 +3,15 @@ // 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.Diagnostics.CodeAnalysis; + namespace SGReactiveUI.SourceGenerators.Test; /// /// TestAttribute. /// /// +[ExcludeFromCodeCoverage] [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)] public sealed class TestAttribute : Attribute { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs index 130a438..61b873e 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestClassOAPH_VM.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestClassOAPH VM. /// +[ExcludeFromCodeCoverage] public partial class TestClassOAPH_VM : ReactiveObject { [ObservableAsProperty] diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index b17debf..a433842 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -28,6 +28,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// /// +[ExcludeFromCodeCoverage] [DataContract] public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDisposable { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel2.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel2.cs index f36a78f..e42cebc 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel2.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel2.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -13,6 +14,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// the type. /// +[ExcludeFromCodeCoverage] public partial class TestViewModel2 : ReactiveObject { [Reactive] diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel3.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel3.cs index 8be61df..e840c10 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel3.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel3.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestViewModel3. /// +[ExcludeFromCodeCoverage] public partial class TestViewModel3 : ReactiveObject { [Reactive] diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel{partTwo}.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel{partTwo}.cs index 8a75897..10ed9b5 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel{partTwo}.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel{partTwo}.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI.SourceGenerators; namespace SGReactiveUI.SourceGenerators.Test @@ -11,6 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test /// TestViewModel. /// /// + [ExcludeFromCodeCoverage] public partial class TestViewModel { /// diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs index b855746..e3e456c 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using System.Windows; using ReactiveUI; using ReactiveUI.SourceGenerators; @@ -13,6 +14,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestView. /// +[ExcludeFromCodeCoverage] [IViewFor(RegistrationType = SplatRegistrationType.PerRequest, ViewModelRegistrationType = SplatRegistrationType.Constant)] public partial class TestViewWpf : Window { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs index 5015e97..5ae3081 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using System.Windows; using ReactiveUI.SourceGenerators; @@ -12,6 +13,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// TestViewWpf2. /// /// +[ExcludeFromCodeCoverage] [IViewFor("SGReactiveUI.SourceGenerators.Test.TestViewModel2", RegistrationType = SplatRegistrationType.PerRequest)] public partial class TestViewWpf2 : Window { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs index 6c3e022..bc35aed 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI.SourceGenerators.WinForms; namespace SGReactiveUI.SourceGenerators.Test; @@ -10,5 +11,6 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestWinFormsRCHost. /// +[ExcludeFromCodeCoverage] [RoutedControlHost(nameof(UserControl))] public partial class TestWinFormsRCHost; diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs index 0b03ab8..d9d192f 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs @@ -3,6 +3,7 @@ // 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.Diagnostics.CodeAnalysis; using ReactiveUI.SourceGenerators.WinForms; namespace SGReactiveUI.SourceGenerators.Test; @@ -13,5 +14,6 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// /// +[ExcludeFromCodeCoverage] [ViewModelControlHost(nameof(UserControl))] public partial class TestWinFormsVMCHost; From 88af2532bdb6088d62a7a26a17dfadf33b04dd45 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 00:32:34 +0000 Subject: [PATCH 3/8] Remove ExcludeFromCodeCoverage attributes from test classes Deleted the [ExcludeFromCodeCoverage] attribute from several test-related classes to allow code coverage tools to include these files. This change may help improve visibility into test code coverage metrics. --- src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs | 1 - src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs | 1 - src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs | 1 - src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs | 1 - src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs | 1 - src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs | 1 - 6 files changed, 6 deletions(-) diff --git a/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs b/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs index 50e3f47..178e865 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Maui/IViewForTest.cs @@ -12,7 +12,6 @@ namespace SGReactiveUI.SourceGenerators.Test.Maui /// IViewForTest. /// /// - [ExcludeFromCodeCoverage] [IViewFor] public partial class IViewForTest : Shell; } diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index a433842..b17debf 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -28,7 +28,6 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// /// -[ExcludeFromCodeCoverage] [DataContract] public partial class TestViewModel : ReactiveObject, IActivatableViewModel, IDisposable { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs index e3e456c..e9fc2ff 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs @@ -14,7 +14,6 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestView. /// -[ExcludeFromCodeCoverage] [IViewFor(RegistrationType = SplatRegistrationType.PerRequest, ViewModelRegistrationType = SplatRegistrationType.Constant)] public partial class TestViewWpf : Window { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs index 5ae3081..3717247 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs @@ -13,7 +13,6 @@ namespace SGReactiveUI.SourceGenerators.Test; /// TestViewWpf2. /// /// -[ExcludeFromCodeCoverage] [IViewFor("SGReactiveUI.SourceGenerators.Test.TestViewModel2", RegistrationType = SplatRegistrationType.PerRequest)] public partial class TestViewWpf2 : Window { diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs index bc35aed..615d5b5 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsRCHost.cs @@ -11,6 +11,5 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// TestWinFormsRCHost. /// -[ExcludeFromCodeCoverage] [RoutedControlHost(nameof(UserControl))] public partial class TestWinFormsRCHost; diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs index d9d192f..16e3ce2 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestWinFormsVMCHost.cs @@ -14,6 +14,5 @@ namespace SGReactiveUI.SourceGenerators.Test; /// /// /// -[ExcludeFromCodeCoverage] [ViewModelControlHost(nameof(UserControl))] public partial class TestWinFormsVMCHost; From d1c6e2beccd52b9dd385414cbde7297c8f7d19ba Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 01:55:46 +0000 Subject: [PATCH 4/8] Fix grouping key in ViewModel registrations Changed the grouping key from SplatRegistrationType to SplatViewModelRegistrationType in viewModelRegistrations to ensure correct grouping of ViewModel registration types. --- .../IViewFor/IViewForGenerator.Execute.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs index fbb0525..e3bc4d7 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs @@ -337,7 +337,7 @@ private static string GenerateRegistrationExtensions(in ImmutableArray !string.IsNullOrWhiteSpace(x.SplatViewModelRegistrationType)) - .GroupBy(static x => (x.TargetInfo.TargetNamespaceWithNamespace, x.ViewModelTypeName, x.SplatRegistrationType)) + .GroupBy(static x => (x.TargetInfo.TargetNamespaceWithNamespace, x.ViewModelTypeName, x.SplatViewModelRegistrationType)) .Select(static g => g.First()) .ToImmutableArray(); From c66d222c14121a9b12c20267212ba3ae89a250d8 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 01:58:12 +0000 Subject: [PATCH 5/8] Clarify Splat registration type documentation Updated XML comments to specify that the ViewModel (T) is registered in the Splat service locator, instead of IViewFor. This improves clarity for consumers of the attribute. --- .../AttributeDefinitions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs index ad1cef8..e91f13c 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs @@ -362,7 +362,7 @@ internal sealed class IViewForAttribute : global::System.Attribute /// /// Gets the Splat registration type for Splat View Model registration. - /// Registers IViewFor in the Splat service locator. + /// Registers the ViewModel (T) in the Splat service locator. /// public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } @@ -387,7 +387,7 @@ internal sealed class IViewForAttribute(string? viewModelType) : global::System. /// /// Gets the Splat registration type for Splat View Model registration. - /// Registers IViewFor in the Splat service locator. + /// Registers the ViewModel (T) in the Splat service locator. /// public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } From d281bcd49c651c1bd476aa01437790a574554b87 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 01:59:50 +0000 Subject: [PATCH 6/8] Update IViewForAttribute XML docs for Splat registration Clarifies XML documentation to specify that ViewModel (T) is registered in the Splat service locator, instead of IViewFor. Adds new received test file for .NET 10.0 verifying the generated attribute code. --- ...eactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs index 85c179c..aea283b 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.SourceGenerators.IViewForAttribute.g.verified.cs @@ -28,7 +28,7 @@ internal sealed class IViewForAttribute : global::System.Attribute /// /// Gets the Splat registration type for Splat View Model registration. - /// Registers IViewFor in the Splat service locator. + /// Registers the ViewModel (T) in the Splat service locator. /// public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } @@ -52,7 +52,7 @@ internal sealed class IViewForAttribute(string? viewModelType) : global::System. /// /// Gets the Splat registration type for Splat View Model registration. - /// Registers IViewFor in the Splat service locator. + /// Registers the ViewModel (T) in the Splat service locator. /// public SplatRegistrationType ViewModelRegistrationType { get; init; } = SplatRegistrationType.None; } From 8a52b4aaccff96dfe958e49a2de5d76178ec92d6 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Mon, 8 Dec 2025 02:10:45 +0000 Subject: [PATCH 7/8] Skip registrations with empty ViewModelTypeName Added checks to skip processing registrations where ViewModelTypeName is null, empty, or whitespace. This prevents potential errors from invalid or incomplete registration data. --- .../IViewFor/IViewForGenerator.Execute.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs index e3bc4d7..a7b3da3 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs @@ -346,6 +346,12 @@ private static string GenerateRegistrationExtensions(in ImmutableArray Date: Mon, 8 Dec 2025 02:16:37 +0000 Subject: [PATCH 8/8] Remove unnecessary null/empty check for vmType Simplifies vmType handling by removing redundant string.IsNullOrEmpty checks before prefixing with 'global::'. This change assumes vmType is always non-null and non-empty at this point in the code. --- .../IViewFor/IViewForGenerator.Execute.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs index a7b3da3..700bfef 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs @@ -105,7 +105,7 @@ public partial class IViewForGenerator token.ThrowIfCancellationRequested(); - // Get RegistrationType enum value from the attribute + // Get ViewModelRegistrationType enum value from the attribute attributeData.TryGetNamedArgument("ViewModelRegistrationType", out int splatViewModelRegistrationType); var viewModelRegistrationType = splatViewModelRegistrationType switch { @@ -352,7 +352,7 @@ private static string GenerateRegistrationExtensions(in ImmutableArray