Skip to content

Commit 32285a1

Browse files
committed
Add warning for unnecessary [NotifyDataErrorInfo] attribute
1 parent 0594506 commit 32285a1

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ 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
38+
MVVMTK0029 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/error
39+
MVVMTK0030 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | 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
@@ -92,6 +92,7 @@ internal static class Execute
9292
bool notifyRecipients = false;
9393
bool notifyDataErrorInfo = false;
9494
bool hasOrInheritsClassLevelNotifyRecipients = false;
95+
bool hasOrInheritsClassLevelNotifyDataErrorInfo = false;
9596
bool hasAnyValidationAttributes = false;
9697

9798
// Track the property changing event for the property, if the type supports it
@@ -114,6 +115,7 @@ internal static class Execute
114115
if (TryGetNotifyDataErrorInfo(fieldSymbol, out bool isValidationTargetValid))
115116
{
116117
notifyDataErrorInfo = isValidationTargetValid;
118+
hasOrInheritsClassLevelNotifyDataErrorInfo = true;
117119
}
118120

119121
// Gather attributes info
@@ -135,7 +137,7 @@ internal static class Execute
135137
}
136138

137139
// Check whether the property should also be validated
138-
if (TryGetNotifyDataErrorInfo(fieldSymbol, attributeData, builder, out isValidationTargetValid))
140+
if (TryGetNotifyDataErrorInfo(fieldSymbol, attributeData, builder, hasOrInheritsClassLevelNotifyDataErrorInfo, out isValidationTargetValid))
139141
{
140142
notifyDataErrorInfo = isValidationTargetValid;
141143

@@ -557,16 +559,28 @@ private static bool TryGetNotifyDataErrorInfo(IFieldSymbol fieldSymbol, out bool
557559
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
558560
/// <param name="attributeData">The <see cref="AttributeData"/> instance for <paramref name="fieldSymbol"/>.</param>
559561
/// <param name="diagnostics">The current collection of gathered diagnostics.</param>
562+
/// <param name="hasOrInheritsClassLevelNotifyDataErrorInfo">Indicates wether the containing type of <paramref name="fieldSymbol"/> has or inherits <c>[NotifyDataErrorInfo]</c>.</param>
560563
/// <param name="isValidationTargetValid">Whether or not the the property is in a valid target that can validate values.</param>
561564
/// <returns>Whether or not the generated property for <paramref name="fieldSymbol"/> used <c>[NotifyDataErrorInfo]</c>.</returns>
562565
private static bool TryGetNotifyDataErrorInfo(
563566
IFieldSymbol fieldSymbol,
564567
AttributeData attributeData,
565568
ImmutableArray<Diagnostic>.Builder diagnostics,
569+
bool hasOrInheritsClassLevelNotifyDataErrorInfo,
566570
out bool isValidationTargetValid)
567571
{
568572
if (attributeData.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") == true)
569573
{
574+
// Emit a diagnostic if the attribute is unnecessary
575+
if (hasOrInheritsClassLevelNotifyDataErrorInfo)
576+
{
577+
diagnostics.Add(
578+
UnnecessaryNotifyDataErrorInfoAttributeOnFieldWarning,
579+
fieldSymbol,
580+
fieldSymbol.ContainingType,
581+
fieldSymbol.Name);
582+
}
583+
570584
// If the containing type is valid, track it
571585
if (fieldSymbol.ContainingType.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"))
572586
{

CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,4 +475,20 @@ internal static class DiagnosticDescriptors
475475
isEnabledByDefault: true,
476476
description: "Annotating a field with [NotifyRecipients] is not necessary if the containing type has or inherits [NotifyRecipients] at the class-level.",
477477
helpLinkUri: "https://aka.ms/mvvmtoolkit");
478+
479+
/// <summary>
480+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[NotifyDataErrorInfo]</c> is applied to a field in a class with <c>[NotifyDataErrorInfo]</c> used at the class-level.
481+
/// <para>
482+
/// Format: <c>"The field {0}.{1} is annotated with [NotifyDataErrorInfo], but that is not needed since its containing type already uses or inherits [NotifyDataErrorInfo] at the class-level"</c>.
483+
/// </para>
484+
/// </summary>
485+
public static readonly DiagnosticDescriptor UnnecessaryNotifyDataErrorInfoAttributeOnFieldWarning = new DiagnosticDescriptor(
486+
id: "MVVMTK0030",
487+
title: "Unnecessary [NotifyDataErrorInfo] field annotation",
488+
messageFormat: "The field {0}.{1} is annotated with [NotifyDataErrorInfo], but that is not needed since its containing type already uses or inherits [NotifyDataErrorInfo] at the class-level",
489+
category: typeof(ObservablePropertyGenerator).FullName,
490+
defaultSeverity: DiagnosticSeverity.Warning,
491+
isEnabledByDefault: true,
492+
description: "Annotating a field with [NotifyDataErrorInfo] is not necessary if the containing type has or inherits [NotifyDataErrorInfo] at the class-level.",
493+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
478494
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,54 @@ public partial class MyViewModel : MyBaseViewModel
13421342
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0029");
13431343
}
13441344

1345+
[TestMethod]
1346+
public void UnnecessaryNotifyDataErrorInfoWarning_SameType()
1347+
{
1348+
string source = @"
1349+
using System.ComponentModel.DataAnnotations;
1350+
using CommunityToolkit.Mvvm.ComponentModel;
1351+
1352+
namespace MyApp
1353+
{
1354+
[NotifyDataErrorInfo]
1355+
public partial class MyViewModel : ObservableValidator
1356+
{
1357+
[ObservableProperty]
1358+
[Required]
1359+
[NotifyDataErrorInfo]
1360+
public int number;
1361+
}
1362+
}";
1363+
1364+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0030");
1365+
}
1366+
1367+
[TestMethod]
1368+
public void UnnecessaryNotifyDataErrorInfoWarning_BaseType()
1369+
{
1370+
string source = @"
1371+
using System.ComponentModel.DataAnnotations;
1372+
using CommunityToolkit.Mvvm.ComponentModel;
1373+
1374+
namespace MyApp
1375+
{
1376+
[NotifyDataErrorInfo]
1377+
public class MyBaseViewModel : ObservableValidator
1378+
{
1379+
}
1380+
1381+
public partial class MyViewModel : MyBaseViewModel
1382+
{
1383+
[ObservableProperty]
1384+
[Required]
1385+
[NotifyDataErrorInfo]
1386+
public int number;
1387+
}
1388+
}";
1389+
1390+
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0030");
1391+
}
1392+
13451393
/// <summary>
13461394
/// Verifies the output of a source generator.
13471395
/// </summary>

0 commit comments

Comments
 (0)