Skip to content

Commit 5e8096f

Browse files
committed
Add AsyncVoidReturningRelayCommandMethodAnalyzer
1 parent 7c3473a commit 5e8096f

File tree

4 files changed

+77
-0
lines changed

4 files changed

+77
-0
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,4 @@ Rule ID | Category | Severity | Notes
6666
--------|----------|----------|-------
6767
MVVMTK0037 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0037
6868
MVVMTK0038 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0038
69+
MVVMTK0039 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0039

src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\ObservableValidatorValidateAllPropertiesGenerator.Execute.cs" />
4040
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.cs" />
4141
<Compile Include="$(MSBuildThisFileDirectory)ComponentModel\TransitiveMembersGenerator.Execute.cs" />
42+
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\AsyncVoidReturningRelayCommandMethodAnalyzer.cs" />
4243
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs" />
4344
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs" />
4445
<Compile Include="$(MSBuildThisFileDirectory)Diagnostics\Analyzers\ClassUsingAttributeInsteadOfInheritanceAnalyzer.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Immutable;
6+
using System.Linq;
7+
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.Diagnostics;
10+
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
11+
12+
namespace CommunityToolkit.Mvvm.SourceGenerators;
13+
14+
/// <summary>
15+
/// A diagnostic analyzer that generates a warning when using <c>[RelayCommand]</c> over an <see langword="async"/> <see cref="void"/> method.
16+
/// </summary>
17+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
18+
public sealed class AsyncVoidReturningRelayCommandMethodAnalyzer : DiagnosticAnalyzer
19+
{
20+
/// <inheritdoc/>
21+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(AsyncVoidReturningRelayCommandMethod);
22+
23+
/// <inheritdoc/>
24+
public override void Initialize(AnalysisContext context)
25+
{
26+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
27+
context.EnableConcurrentExecution();
28+
29+
context.RegisterCompilationStartAction(static context =>
30+
{
31+
// Get the symbol for [RelayCommand]
32+
if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.Input.RelayCommandAttribute") is not INamedTypeSymbol relayCommandSymbol)
33+
{
34+
return;
35+
}
36+
37+
context.RegisterSymbolAction(context =>
38+
{
39+
// We're only looking for async void methods
40+
if (context.Symbol is not IMethodSymbol { IsAsync: true, ReturnsVoid: true } methodSymbol)
41+
{
42+
return;
43+
}
44+
45+
// We only care about methods annotated with [RelayCommand]
46+
if (!methodSymbol.HasAttributeWithType(relayCommandSymbol))
47+
{
48+
return;
49+
}
50+
51+
// Warn on async void methods using [RelayCommand] (they should return a Task instead)
52+
context.ReportDiagnostic(Diagnostic.Create(
53+
AsyncVoidReturningRelayCommandMethod,
54+
context.Symbol.Locations.FirstOrDefault(),
55+
context.Symbol));
56+
}, SymbolKind.Method);
57+
});
58+
}
59+
}

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,4 +637,20 @@ internal static class DiagnosticDescriptors
637637
isEnabledByDefault: true,
638638
description: "All attributes targeting the generated field or property for a method annotated with [RelayCommand] must be using valid expressions.",
639639
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0038");
640+
641+
/// <summary>
642+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when a method with <c>[RelayCommand]</c> is async void.
643+
/// <para>
644+
/// Format: <c>"The method {0} annotated with [RelayCommand] is async void (make sure to return a Task type instead)"</c>.
645+
/// </para>
646+
/// </summary>
647+
public static readonly DiagnosticDescriptor AsyncVoidReturningRelayCommandMethod = new DiagnosticDescriptor(
648+
id: "MVVMTK0039",
649+
title: "Async void returning method annotated with RelayCommand",
650+
messageFormat: "The method {0} annotated with [RelayCommand] is async void (make sure to return a Task type instead)",
651+
category: typeof(RelayCommandGenerator).FullName,
652+
defaultSeverity: DiagnosticSeverity.Warning,
653+
isEnabledByDefault: true,
654+
description: "All asynchronous methods annotated with [RelayCommand] should return a Task type, to benefit from the additional support provided by AsyncRelayCommand and AsyncRelayCommand<T>.",
655+
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0039");
640656
}

0 commit comments

Comments
 (0)