diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs new file mode 100644 index 0000000..1462b40 --- /dev/null +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveCommandWithNullableTypeAndNullableReturnType#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs @@ -0,0 +1,44 @@ +//HintName: ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.cs +// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +// +#pragma warning disable +#nullable enable +namespace ReactiveUI.SourceGenerators; + +/// +/// ReativeCommandAttribute. +/// +/// +[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +internal sealed class ReactiveCommandAttribute : global::System.Attribute +{ + /// + /// Gets the can execute method or property. + /// + /// + /// The name of the CanExecute Observable of bool. + /// + public string? CanExecute { get; init; } + + /// + /// Gets the output scheduler. + /// + /// + /// The output scheduler. + /// + public string? OutputScheduler { get; init; } + + /// + /// Gets the AccessModifier of the ReactiveCommand property. + /// + /// + /// The AccessModifier of the property. + /// + public PropertyAccessModifier AccessModifier { get; init; } +} +#nullable restore +#pragma warning restore \ No newline at end of file diff --git a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs index 6ab4cc6..4b7493c 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/UnitTests/ReactiveCMDGeneratorTests.cs @@ -238,4 +238,31 @@ public partial class TestVM : ReactiveObject """; return TestHelper.TestPass(sourceCode); } + + /// + /// Froms the type of the reactive command with nullable type and nullable return. + /// + /// A task to monitor the async. + [Test] + public Task FromReactiveCommandWithNullableTypeAndNullableReturnType() + { + const string sourceCode = """ + using System; + using ReactiveUI; + using ReactiveUI.SourceGenerators; + namespace TestNs; + + public class NullableInput + { + public string? Name { get; set; } + } + + public partial class TestVM : ReactiveObject + { + [ReactiveCommand] + private NullableInput? Test1(NullableInput? input) => input; + } + """; + return TestHelper.TestPass(sourceCode); + } } diff --git a/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs index 9f7f0e4..3e8179b 100644 --- a/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs +++ b/src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using ReactiveUI; using ReactiveUI.SourceGenerators; +using SGReactiveUI.SourceGenerators.Execute.Nested2; namespace SGReactiveUI.SourceGenerators.Execute.Nested3; @@ -17,4 +18,23 @@ public partial class Class1 : ReactiveObject { [Reactive] private string? _property1; + + /// + /// Initializes a new instance of the class. + /// + public Class1() + { + SetPropertyCommand.Execute(new Nested1.Class1 { Property1 = "Initial Value" }).Subscribe(); + } + + [ReactiveCommand] + private SGReactiveUI.SourceGenerators.Execute.Nested2.Class1? SetProperty(Nested1.Class1? class1) + { + if (class1 == null) + { + return null; + } + + return new() { Property1 = class1.Property1 }; + } } diff --git a/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj b/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj index bd130b1..8c5b259 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj +++ b/src/ReactiveUI.SourceGenerators.Execute/ReactiveUI.SourceGenerators.Execute.csproj @@ -20,6 +20,7 @@ + diff --git a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs index 1e26857..e0f51a7 100644 --- a/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs +++ b/src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs @@ -16,6 +16,7 @@ using DynamicData; using ReactiveUI; using ReactiveUI.SourceGenerators; +using SGReactiveUI.SourceGenerators.Execute.Nested3; namespace SGReactiveUI.SourceGenerators.Test; @@ -452,4 +453,15 @@ protected virtual void Dispose(bool disposing) [ReactiveCommand] private Task GetData(CancellationToken ct) => Task.FromResult(Array.Empty()); + + [ReactiveCommand] + private Execute.Nested2.Class1? SetProperty(Execute.Nested1.Class1? class1) + { + if (class1 == null) + { + return null; + } + + return new() { Property1 = class1.Property1 }; + } } diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.Execute.cs index dd3718b..6fef1b7 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.Execute.cs @@ -3,7 +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.Collections.Generic; +using System; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -15,7 +15,6 @@ using ReactiveUI.SourceGenerators.Helpers; using ReactiveUI.SourceGenerators.Input.Models; using ReactiveUI.SourceGenerators.Models; -using static ReactiveUI.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace ReactiveUI.SourceGenerators; @@ -120,11 +119,16 @@ public partial class ReactiveCommandGenerator token.ThrowIfCancellationRequested(); + var argumentType = methodParameters.ToImmutable().SingleOrDefault()?.Type; + var argumentTypeString = argumentType?.GetFullyQualifiedNameWithNullabilityAnnotations(); + + token.ThrowIfCancellationRequested(); + return new( targetInfo, symbol.Name, realReturnType.GetFullyQualifiedNameWithNullabilityAnnotations(), - methodParameters.ToImmutable().SingleOrDefault()?.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + argumentTypeString, isTask, isReturnTypeVoid, isObservable, @@ -138,7 +142,7 @@ public partial class ReactiveCommandGenerator private static string GenerateSource(string containingTypeName, string containingNamespace, string containingClassVisibility, string containingType, CommandInfo[] commands) { // Get Parent class details from properties.ParentInfo - var (parentClassDeclarationsString, closingBrackets) = TargetInfo.GenerateParentClassDeclarations(commands.Select(p => p.TargetInfo.ParentInfo).ToArray()); + var (parentClassDeclarationsString, closingBrackets) = TargetInfo.GenerateParentClassDeclarations([.. commands.Select(p => p.TargetInfo.ParentInfo)]); var classes = GenerateClassWithCommands(containingTypeName, containingNamespace, containingClassVisibility, containingType, commands); diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs index 86e5acb..e40aef2 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/ReactiveCommand/ReactiveCommandGenerator.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -using ReactiveUI.SourceGenerators.Extensions; using ReactiveUI.SourceGenerators.Helpers; namespace ReactiveUI.SourceGenerators;