Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -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.

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

/// <summary>
/// ReativeCommandAttribute.
/// </summary>
/// <seealso cref="Attribute" />
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class ReactiveCommandAttribute : global::System.Attribute
{
/// <summary>
/// Gets the can execute method or property.
/// </summary>
/// <value>
/// The name of the CanExecute Observable of bool.
/// </value>
public string? CanExecute { get; init; }

/// <summary>
/// Gets the output scheduler.
/// </summary>
/// <value>
/// The output scheduler.
/// </value>
public string? OutputScheduler { get; init; }

/// <summary>
/// Gets the AccessModifier of the ReactiveCommand property.
/// </summary>
/// <value>
/// The AccessModifier of the property.
/// </value>
public PropertyAccessModifier AccessModifier { get; init; }
}
#nullable restore
#pragma warning restore
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,31 @@ public partial class TestVM : ReactiveObject
""";
return TestHelper.TestPass(sourceCode);
}

/// <summary>
/// Froms the type of the reactive command with nullable type and nullable return.
/// </summary>
/// <returns>A task to monitor the async.</returns>
[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);
}
}
20 changes: 20 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute.Nested3/Class1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics.CodeAnalysis;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using SGReactiveUI.SourceGenerators.Execute.Nested2;

namespace SGReactiveUI.SourceGenerators.Execute.Nested3;

Expand All @@ -17,4 +18,23 @@ public partial class Class1 : ReactiveObject
{
[Reactive]
private string? _property1;

/// <summary>
/// Initializes a new instance of the <see cref="Class1"/> class.
/// </summary>
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 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

<ItemGroup>
<ProjectReference Include="..\ReactiveUI.SourceGenerators.Analyzers.CodeFixes\ReactiveUI.SourceGenerators.Analyzers.CodeFixes.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\ReactiveUI.SourceGenerators.Execute.Nested3\ReactiveUI.SourceGenerators.Execute.Nested3.csproj" />
<ProjectReference Include="..\ReactiveUI.SourceGenerators.Roslyn4120\ReactiveUI.SourceGenerators.Roslyn4120.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
12 changes: 12 additions & 0 deletions src/ReactiveUI.SourceGenerators.Execute/TestViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using DynamicData;
using ReactiveUI;
using ReactiveUI.SourceGenerators;
using SGReactiveUI.SourceGenerators.Execute.Nested3;

namespace SGReactiveUI.SourceGenerators.Test;

Expand Down Expand Up @@ -452,4 +453,15 @@ protected virtual void Dispose(bool disposing)
[ReactiveCommand]
private Task<System.Collections.IEnumerable> GetData(CancellationToken ct) =>
Task.FromResult<System.Collections.IEnumerable>(Array.Empty<System.Collections.IEnumerable>());

[ReactiveCommand]
private Execute.Nested2.Class1? SetProperty(Execute.Nested1.Class1? class1)
{
if (class1 == null)
{
return null;
}

return new() { Property1 = class1.Property1 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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,
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading