Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Fix [RelayCommand] attribute over methods with no attributes
  • Loading branch information
Sergio0694 committed Mar 9, 2023
commit 66f582bd94a8bd922661ae9ea960fd144fb232ca
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\INamedTypeSymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalGeneratorInitializationContextExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\IncrementalValuesProviderExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\MethodDeclarationSyntaxExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SymbolInfoExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ISymbolExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\SourceProductionContextExtensions.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 more information.

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions;

/// <summary>
/// Extension methods for the <see cref="MethodDeclarationSyntax"/> type.
/// </summary>
internal static class MethodDeclarationSyntaxExtensions
{
/// <summary>
/// Checks whether a given <see cref="MethodDeclarationSyntax"/> has or could potentially have any attribute lists.
/// </summary>
/// <param name="methodDeclaration">The input <see cref="MethodDeclarationSyntax"/> to check.</param>
/// <returns>Whether <paramref name="methodDeclaration"/> has or potentially has any attribute lists.</returns>
public static bool HasOrPotentiallyHasAttributeLists(this MethodDeclarationSyntax methodDeclaration)
{
// If the declaration has any attribute lists, there's nothing left to do
if (methodDeclaration.AttributeLists.Count > 0)
{
return true;
}

// If there are no attributes, check whether the method declaration has the partial keyword. If it
// does, there could potentially be attribute lists on the other partial definition/implementation.
return methodDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ internal static class SyntaxNodeExtensions
public static bool IsFirstSyntaxDeclarationForSymbol(this SyntaxNode syntaxNode, ISymbol symbol)
{
return
symbol.DeclaringSyntaxReferences.Length > 0 &&
symbol.DeclaringSyntaxReferences[0] is SyntaxReference syntaxReference &&
symbol.DeclaringSyntaxReferences is [SyntaxReference syntaxReference, ..] &&
syntaxReference.SyntaxTree == syntaxNode.SyntaxTree &&
syntaxReference.Span == syntaxNode.Span;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
context.SyntaxProvider
.ForAttributeWithMetadataName(
"CommunityToolkit.Mvvm.Input.RelayCommandAttribute",
static (node, _) => node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax, AttributeLists.Count: > 0 },
static (node, _) => node is MethodDeclarationSyntax { Parent: ClassDeclarationSyntax } methodDeclaration && methodDeclaration.HasOrPotentiallyHasAttributeLists(),
static (context, token) =>
{
if (!context.SemanticModel.Compilation.HasLanguageVersionAtLeastEqualTo(LanguageVersion.CSharp8))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,76 @@ partial class MyViewModel
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
}

// See https://github.com/CommunityToolkit/dotnet/issues/632
[TestMethod]
public void RelayCommandMethodWithPartialDeclarations_TriggersCorrectly()
{
string source = """
using CommunityToolkit.Mvvm.Input;

#nullable enable

namespace MyApp;

partial class MyViewModel
{
[RelayCommand]
private partial void Test1()
{
}

private partial void Test1();

private partial void Test2()
{
}

[RelayCommand]
private partial void Test2();
}
""";

string result1 = """
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp
{
partial class MyViewModel
{
/// <summary>The backing field for <see cref="Test1Command"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
private global::CommunityToolkit.Mvvm.Input.RelayCommand? test1Command;
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test1"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test1Command => test1Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test1));
}
}
""";

string result2 = """
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace MyApp
{
partial class MyViewModel
{
/// <summary>The backing field for <see cref="Test2Command"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
private global::CommunityToolkit.Mvvm.Input.RelayCommand? test2Command;
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test2"/>.</summary>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public global::CommunityToolkit.Mvvm.Input.IRelayCommand Test2Command => test2Command ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test2));
}
}
""";

VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test1.g.cs", result1), ("MyApp.MyViewModel.Test2.g.cs", result2));
}

// See https://github.com/CommunityToolkit/dotnet/issues/632
[TestMethod]
public void RelayCommandMethodWithForwardedAttributesOverPartialDeclarations_MergesAttributes()
Expand Down