Skip to content

Commit 0594506

Browse files
committed
Add warning for unnecessary [NotifyRecipients] attribute
1 parent 4a3f919 commit 0594506

File tree

4 files changed

+76
-1
lines changed

4 files changed

+76
-1
lines changed

CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ MVVMTK0025 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
3535
MVVMTK0026 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3636
MVVMTK0027 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3737
MVVMTK0028 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
38+
MVVMTK0029 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ internal static class Execute
9191
ImmutableArray<AttributeInfo>.Builder forwardedAttributes = ImmutableArray.CreateBuilder<AttributeInfo>();
9292
bool notifyRecipients = false;
9393
bool notifyDataErrorInfo = false;
94+
bool hasOrInheritsClassLevelNotifyRecipients = false;
9495
bool hasAnyValidationAttributes = false;
9596

9697
// Track the property changing event for the property, if the type supports it
@@ -106,6 +107,7 @@ internal static class Execute
106107
if (TryGetIsNotifyingRecipients(fieldSymbol, out bool isBroadcastTargetValid))
107108
{
108109
notifyRecipients = isBroadcastTargetValid;
110+
hasOrInheritsClassLevelNotifyRecipients = true;
109111
}
110112

111113
// Get the class-level [NotifyDataErrorInfo] setting, if any
@@ -125,7 +127,7 @@ internal static class Execute
125127
}
126128

127129
// Check whether the property should also notify recipients
128-
if (TryGetIsNotifyingRecipients(fieldSymbol, attributeData, builder, out isBroadcastTargetValid))
130+
if (TryGetIsNotifyingRecipients(fieldSymbol, attributeData, builder, hasOrInheritsClassLevelNotifyRecipients, out isBroadcastTargetValid))
129131
{
130132
notifyRecipients = isBroadcastTargetValid;
131133

@@ -452,16 +454,28 @@ private static bool TryGetIsNotifyingRecipients(IFieldSymbol fieldSymbol, out bo
452454
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
453455
/// <param name="attributeData">The <see cref="AttributeData"/> instance for <paramref name="fieldSymbol"/>.</param>
454456
/// <param name="diagnostics">The current collection of gathered diagnostics.</param>
457+
/// <param name="hasOrInheritsClassLevelNotifyRecipients">Indicates wether the containing type of <paramref name="fieldSymbol"/> has or inherits <c>[NotifyRecipients]</c>.</param>
455458
/// <param name="isBroadcastTargetValid">Whether or not the the property is in a valid target that can notify recipients.</param>
456459
/// <returns>Whether or not the generated property for <paramref name="fieldSymbol"/> used <c>[NotifyRecipients]</c>.</returns>
457460
private static bool TryGetIsNotifyingRecipients(
458461
IFieldSymbol fieldSymbol,
459462
AttributeData attributeData,
460463
ImmutableArray<Diagnostic>.Builder diagnostics,
464+
bool hasOrInheritsClassLevelNotifyRecipients,
461465
out bool isBroadcastTargetValid)
462466
{
463467
if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyRecipientsAttribute") == true)
464468
{
469+
// Emit a diagnostic if the attribute is unnecessary
470+
if (hasOrInheritsClassLevelNotifyRecipients)
471+
{
472+
diagnostics.Add(
473+
UnnecessaryNotifyRecipientsAttributeOnFieldWarning,
474+
fieldSymbol,
475+
fieldSymbol.ContainingType,
476+
fieldSymbol.Name);
477+
}
478+
465479
// If the containing type is valid, track it
466480
if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") ||
467481
fieldSymbol.ContainingType.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute"))

CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,4 +459,20 @@ internal static class DiagnosticDescriptors
459459
isEnabledByDefault: true,
460460
description: "Types annotated with [NotifyDataErrorInfo] must inherit from ObservableRecipient.",
461461
helpLinkUri: "https://aka.ms/mvvmtoolkit");
462+
463+
/// <summary>
464+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[NotifyRecipients]</c> is applied to a field in a class with <c>[NotifyRecipients]</c> used at the class-level.
465+
/// <para>
466+
/// Format: <c>"The field {0}.{1} is annotated with [NotifyRecipients], but that is not needed since its containing type already uses or inherits [NotifyRecipients] at the class-level"</c>.
467+
/// </para>
468+
/// </summary>
469+
public static readonly DiagnosticDescriptor UnnecessaryNotifyRecipientsAttributeOnFieldWarning = new DiagnosticDescriptor(
470+
id: "MVVMTK0029",
471+
title: "Unnecessary [NotifyRecipients] field annotation",
472+
messageFormat: "The field {0}.{1} is annotated with [NotifyRecipients], but that is not needed since its containing type already uses or inherits [NotifyRecipients] at the class-level",
473+
category: typeof(ObservablePropertyGenerator).FullName,
474+
defaultSeverity: DiagnosticSeverity.Warning,
475+
isEnabledByDefault: true,
476+
description: "Annotating a field with [NotifyRecipients] is not necessary if the containing type has or inherits [NotifyRecipients] at the class-level.",
477+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
462478
}

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,6 +1298,50 @@ public partial class SampleViewModel : ObservableObject
12981298
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0006", "MVVMTK0028");
12991299
}
13001300

1301+
[TestMethod]
1302+
public void UnnecessaryNotifyRecipientsWarning_SameType()
1303+
{
1304+
string source = @"
1305+
using CommunityToolkit.Mvvm.ComponentModel;
1306+
1307+
namespace MyApp
1308+
{
1309+
[NotifyRecipients]
1310+
public partial class MyViewModel : ObservableRecipient
1311+
{
1312+
[ObservableProperty]
1313+
[NotifyRecipients]
1314+
public int number;
1315+
}
1316+
}";
1317+
1318+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0029");
1319+
}
1320+
1321+
[TestMethod]
1322+
public void UnnecessaryNotifyRecipientsWarning_BaseType()
1323+
{
1324+
string source = @"
1325+
using CommunityToolkit.Mvvm.ComponentModel;
1326+
1327+
namespace MyApp
1328+
{
1329+
[NotifyRecipients]
1330+
public class MyBaseViewModel : ObservableRecipient
1331+
{
1332+
}
1333+
1334+
public partial class MyViewModel : MyBaseViewModel
1335+
{
1336+
[ObservableProperty]
1337+
[NotifyRecipients]
1338+
public int number;
1339+
}
1340+
}";
1341+
1342+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0029");
1343+
}
1344+
13011345
/// <summary>
13021346
/// Verifies the output of a source generator.
13031347
/// </summary>

0 commit comments

Comments
 (0)