diff --git a/README.md b/README.md index f9986d6..a4c001e 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,23 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream - `[ReactiveCommand(OutputScheduler = nameof(_isheduler))]` using a Scheduler defined in the class - `[ReactiveCommand][property: AttributeToAddToCommand]` with Attribute passthrough - `[IViewFor(nameof(ViewModelName))]` +- `[IViewFor]` +- `[IViewFor("YourNameSpace.YourGenericViewModel")]` Generic +- `[IViewFor(RegistrationType = SplatRegistrationType.PerRequest)]` with Splat Registration Type for IViewFor registration. +- `[IViewFor(RegistrationType = SplatRegistrationType.LazySingleton)]` Generic with Splat Registration Type for IViewFor registration. +- `[IViewFor(RegistrationType = SplatRegistrationType.Constant)]` Generic with Splat Registration Type for IViewFor registration. - `[RoutedControlHost("YourNameSpace.CustomControl")]` - `[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 +#### IViewFor Registration generator + +To register all views for view models registered via the IViewFor Source Generator with a specified `RegistrationType`, call the following method during application startup: +```csharp +Splat.Locator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated(); +``` + ### Compatibility Notes - For ReactiveUI versions **older than V19.5.31**, all `[ReactiveCommand]` options are supported except for async methods with a `CancellationToken`. - For **.NET Framework 4.8 and older**, add [Polyfill by Simon Cropp](https://github.com/SimonCropp/Polyfill) or [PolySharp by Sergio Pedri](https://github.com/Sergio0694/PolySharp) to your project and set the `LangVersion` to 12.0 or later in your project file. @@ -63,11 +75,17 @@ Generates read-only properties backed by an `ObservableAsPropertyHelper` based o Generates commands, with options to add attributes or enable `CanExecute` functionality. ### `[IViewFor]` -Links a view to a view model for data binding. +Links a view to a view model for data binding. Supports generic types and Splat registration. ### `[RoutedControlHost]` and `[ViewModelControlHost]` Platform-specific attributes for control hosting in WinForms applications. +### `[BindableDerivedList]` +Generates a derived list from a `ReadOnlyObservableCollection` backing field. + +### `[ReactiveCollection]` +Generates property changed notifications on add, remove, and new actions on an `ObservableCollection` backing field. + ## Historical Approach ### Read-Write Properties @@ -525,6 +543,35 @@ The class must inherit from a UI Control from any of the following platforms and - Avalonia (Avalonia) - Uno (Windows.UI.Xaml). +### IViewFor with Splat Registration Type + +Choose from the following Splat Registration Types: +- `SplatRegistrationType.PerRequest` +- `SplatRegistrationType.LazySingleton` +- `SplatRegistrationType.Constant` +- `SplatRegistrationType.None` (Default if not specified - no registration is performed) + +```csharp +using ReactiveUI.SourceGenerators; +using Splat; +[IViewFor(RegistrationType = SplatRegistrationType.PerRequest)] +public partial class MyReactiveControl : UserControl +{ + public MyReactiveControl() + { + InitializeComponent(); + ViewModel = Locator.Current.GetService(); + } +} +``` + +this will generate the following code to enable you register the marked Views as `IViewFor` with Splat: +```csharp +using ReactiveUI.SourceGenerators; + +Splat.Locator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated(); +``` + ### Usage IViewFor with ViewModel Name - Generic Types should be used with the fully qualified name, otherwise use nameof(ViewModelTypeName) ```csharp using ReactiveUI.SourceGenerators; diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8deeeeb..4facfe1 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -9,6 +9,7 @@ + @@ -45,4 +46,4 @@ - + \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.verified.cs new file mode 100644 index 0000000..ee04db7 --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/IVIEWFOR/IViewForGeneratorTests.FromIViewFor#ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.verified.cs @@ -0,0 +1,23 @@ +//HintName: ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.cs +// +#pragma warning disable +#nullable enable + +using global::ReactiveUI; +using global::Splat; + +namespace ReactiveUI.SourceGenerators +{ + /// + /// Source-generated registration extensions for ReactiveUI views. + /// + internal static class ReactiveUISourceGeneratorsExtensions + { + public static void RegisterViewsForViewModelsSourceGenerated(this global::Splat.IMutableDependencyResolver resolver) + { + if (resolver is null) throw new global::System.ArgumentNullException(nameof(resolver)); + } + } +} +#nullable restore +#pragma warning restore \ No newline at end of file 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 0ad03a7..11d67f6 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 @@ -18,7 +18,14 @@ namespace ReactiveUI.SourceGenerators; /// /// Type of the view model. [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -internal sealed class IViewForAttribute : global::System.Attribute; +internal sealed class IViewForAttribute : global::System.Attribute +{ + /// + /// Gets the Splat registration type for Splat registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; +} /// /// IViewForAttribute. @@ -27,8 +34,15 @@ internal sealed class IViewForAttribute : global::System.Attribute; /// /// Initializes a new instance of the class. /// -/// Type of the view model. +/// Type of the view model, ensure to use the full type name including namespace. [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute; +internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute +{ + /// + /// Gets the Splat registration type for Splat registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePartialProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePartialProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePartialProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePartialProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs index 2f7525d..0e3ac88 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#ReactiveUI.SourceGenerators.AccessModifier.g.verified.cs @@ -33,5 +33,13 @@ internal enum InheritanceModifier Override, New, } + +internal enum SplatRegistrationType +{ + None, + LazySingleton, + Constant, + PerRequest, +} #nullable restore #pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerators.Execute/Program.cs b/src/ReactiveUI.SourceGenerators.Execute/Program.cs index df652e7..6db2c3a 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/Program.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/Program.cs @@ -3,6 +3,9 @@ // 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; +using Splat; + namespace SGReactiveUI.SourceGenerators.Test; /// @@ -13,5 +16,9 @@ public static class Program /// /// Defines the entry point of the application. /// - public static void Main() => Application.Run(new TestViewWinForms()); + public static void Main() + { + AppLocator.CurrentMutable.RegisterViewsForViewModelsSourceGenerated(); + Application.Run(new TestViewWinForms()); + } } diff --git a/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj b/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj index 2bdda74..bd130b1 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj +++ b/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj @@ -15,6 +15,7 @@ + diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs index 92e87dc..e73c3d0 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWinForms.cs @@ -11,7 +11,7 @@ namespace SGReactiveUI.SourceGenerators.Test /// TestViewWinForms. /// /// - [IViewFor] + [IViewFor(RegistrationType = SplatRegistrationType.LazySingleton)] public partial class TestViewWinForms : Form { /// diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs index 1045826..1047ca1 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf.cs @@ -6,19 +6,24 @@ using System.Windows; using ReactiveUI; using ReactiveUI.SourceGenerators; +using Splat; namespace SGReactiveUI.SourceGenerators.Test; /// /// TestView. /// -[IViewFor] +[IViewFor(RegistrationType = SplatRegistrationType.PerRequest)] public partial class TestViewWpf : Window { /// /// Initializes a new instance of the class. /// - public TestViewWpf() => ViewModel = TestViewModel.Instance; + public TestViewWpf() + { + Locator.CurrentMutable.RegisterLazySingleton>(() => new TestViewWpf()); + ViewModel = TestViewModel.Instance; + } /// /// Gets or sets the test property. diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs index b7f4fc7..5015e97 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewWpf2.cs @@ -12,7 +12,7 @@ namespace SGReactiveUI.SourceGenerators.Test; /// TestViewWpf2. /// /// -[IViewFor("TestViewModel2")] +[IViewFor("SGReactiveUI.SourceGenerators.Test.TestViewModel2", RegistrationType = SplatRegistrationType.PerRequest)] public partial class TestViewWpf2 : Window { /// diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs index 950d6c3..13d2207 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/AttributeDefinitions.cs @@ -52,6 +52,14 @@ internal enum InheritanceModifier Override, New, } + + internal enum SplatRegistrationType + { + None, + LazySingleton, + Constant, + PerRequest, + } #nullable restore #pragma warning restore """; @@ -71,7 +79,7 @@ namespace ReactiveUI.SourceGenerators; /// ReactiveObjectAttribute. /// /// -[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")] +[global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.ReactiveObjectGenerator", "1.1.0.0")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] internal sealed class ReactiveObjectAttribute : global::System.Attribute; #nullable restore @@ -344,7 +352,14 @@ namespace ReactiveUI.SourceGenerators; /// Type of the view model. [global::System.CodeDom.Compiler.GeneratedCode("ReactiveUI.SourceGenerators.IViewForGenerator", "1.1.0.0")] [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -internal sealed class IViewForAttribute : global::System.Attribute; +internal sealed class IViewForAttribute : global::System.Attribute +{ + /// + /// Gets the Splat registration type for Splat registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; +} /// /// IViewForAttribute. @@ -353,10 +368,17 @@ internal sealed class IViewForAttribute : global::System.Attribute; /// /// Initializes a new instance of the class. /// -/// Type of the view model. +/// 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.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute; +internal sealed class IViewForAttribute(string? viewModelType) : global::System.Attribute +{ + /// + /// Gets the Splat registration type for Splat registration. + /// Registers IViewFor in the Splat service locator. + /// + public SplatRegistrationType RegistrationType { get; init; } = SplatRegistrationType.None; +} #nullable restore #pragma warning restore """; diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs index 2043645..5c061fc 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.Execute.cs @@ -3,7 +3,9 @@ // 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 System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -91,10 +93,21 @@ public partial class IViewForGenerator token.ThrowIfCancellationRequested(); + // Get RegistrationType enum value from the attribute + attributeData.TryGetNamedArgument("RegistrationType", out int splatRegistrationType); + var registrationType = splatRegistrationType switch + { + 1 => "RegisterLazySingleton", + 2 => "RegisterConstant", + 3 => "Register", + _ => string.Empty, + }; + return new( targetInfo, viewModelTypeName!, - viewForBaseType); + viewForBaseType, + registrationType); } private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, IViewForInfo iviewForInfo) @@ -299,4 +312,70 @@ protected override void OnBindingContextChanged() return string.Empty; } + + private static string GenerateRegistrationExtensions(in ImmutableArray iviewForInfo) + { + // Collapse to unique registrations and skip entries with no registration + var registrations = iviewForInfo + .Where(static x => !string.IsNullOrWhiteSpace(x.SplatRegistrationType)) + .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) + { + var vmType = item.ViewModelTypeName; + if (!string.IsNullOrEmpty(vmType) && !vmType.StartsWith("global::", System.StringComparison.Ordinal)) + { + vmType = "global::" + vmType; + } + + var serviceType = "global::ReactiveUI.IViewFor<" + vmType + ">"; + var viewType = item.TargetInfo.TargetNamespaceWithNamespace; // already fully-qualified + + // resolver.Register*/, View>(); + switch (item.SplatRegistrationType) + { + case "RegisterLazySingleton": + sb.AppendLine($" resolver.{item.SplatRegistrationType}<{serviceType}>(() => new {viewType}());"); + break; + case "Register": + sb.AppendLine($" resolver.{item.SplatRegistrationType}<{serviceType}, {viewType}>();"); + break; + case "RegisterConstant": + sb.AppendLine($" resolver.{item.SplatRegistrationType}<{serviceType}>(new {viewType}());"); + break; + } + } + + var registrationsBody = sb.ToString().TrimEnd(); + return + $$""" +// +#pragma warning disable +#nullable enable + +using global::ReactiveUI; +using global::Splat; + +namespace ReactiveUI.SourceGenerators +{ + /// + /// Source-generated registration extensions for ReactiveUI views. + /// + internal static class ReactiveUISourceGeneratorsExtensions + { + [global::System.CodeDom.Compiler.GeneratedCode("{{GeneratorName}}", "{{GeneratorVersion}}")] + public static void RegisterViewsForViewModelsSourceGenerated(this global::Splat.IMutableDependencyResolver resolver) + { + {{registrationsBody}} + } + } +} +#nullable restore +#pragma warning restore +"""; + } } diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs index cb2970b..fef7d26 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs @@ -23,7 +23,7 @@ public sealed partial class IViewForGenerator : IIncrementalGenerator public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(ctx => - ctx.AddSource($"{AttributeDefinitions.IViewForAttributeType}.g.cs", SourceText.From(AttributeDefinitions.IViewForAttribute, Encoding.UTF8))); + ctx.AddSource(AttributeDefinitions.IViewForAttributeType + ".g.cs", SourceText.From(AttributeDefinitions.IViewForAttribute, Encoding.UTF8))); // Gather info for all annotated IViewFor Classes var iViewForInfo = @@ -44,11 +44,20 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static info => info) .ToImmutableArray(); + const string fileName = "ReactiveUI.ReactiveUISourceGeneratorsExtensions.g.cs"; + if (groupedPropertyInfo.Length == 0) { + // Even if there are no views, emit an empty extension to keep API stable. + var empty = GenerateRegistrationExtensions(ImmutableArray.Create()); + context.AddSource(fileName, SourceText.From(empty, Encoding.UTF8)); return; } + // Generate the IViewFor Splat Registration code for all classes in a single extension method here + var registrationSource = GenerateRegistrationExtensions(input); + context.AddSource(fileName, SourceText.From(registrationSource, Encoding.UTF8)); + foreach (var grouping in groupedPropertyInfo) { var items = grouping.ToImmutableArray(); @@ -59,7 +68,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, grouping.FirstOrDefault()); - context.AddSource($"{grouping.Key.FileHintName}.IViewFor.g.cs", source); + context.AddSource(grouping.Key.FileHintName + ".IViewFor.g.cs", source); } }); } diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs index cb6f51a..36a5485 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/Models/IViewForInfo.cs @@ -14,4 +14,5 @@ namespace ReactiveUI.SourceGenerators.Input.Models; internal sealed record IViewForInfo( TargetInfo TargetInfo, string ViewModelTypeName, - IViewForBaseType BaseType); + IViewForBaseType BaseType, + string SplatRegistrationType);