Skip to content

Commit 4d8630b

Browse files
committed
Enable diagnostics for class-level recipient/validation attributes
1 parent 139068f commit 4d8630b

File tree

4 files changed

+100
-0
lines changed

4 files changed

+100
-0
lines changed

CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ MVVMTK0023 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error |
3333
MVVMTK0024 | CommunityToolkit.Mvvm.SourceGenerators.ICommandGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3434
MVVMTK0025 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
3535
MVVMTK0026 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
36+
MVVMTK0027 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error
37+
MVVMTK0028 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/error

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,26 @@ private static bool TryGetIsNotifyingRecipients(
488488
return false;
489489
}
490490

491+
/// <summary>
492+
/// Checks whether a given type using <c>[NotifyRecipients]</c> is valid and creates a <see cref="Diagnostic"/> if not.
493+
/// </summary>
494+
/// <param name="typeSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
495+
/// <returns>The <see cref="Diagnostic"/> for <paramref name="typeSymbol"/>, if not a valid type.</returns>
496+
public static Diagnostic? GetIsNotifyingRecipientsDiagnosticForType(INamedTypeSymbol typeSymbol)
497+
{
498+
// If the containing type is valid, track it
499+
if (!typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") &&
500+
!typeSymbol.HasOrInheritsAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute"))
501+
{
502+
return Diagnostic.Create(
503+
InvalidTypeForNotifyRecipientsError,
504+
typeSymbol.Locations.FirstOrDefault(),
505+
typeSymbol);
506+
}
507+
508+
return null;
509+
}
510+
491511
/// <summary>
492512
/// Checks whether a given generated property should also validate its value.
493513
/// </summary>
@@ -558,6 +578,25 @@ private static bool TryGetNotifyDataErrorInfo(
558578
return false;
559579
}
560580

581+
/// <summary>
582+
/// Checks whether a given type using <c>[NotifyDataErrorInfo]</c> is valid and creates a <see cref="Diagnostic"/> if not.
583+
/// </summary>
584+
/// <param name="typeSymbol">The input <see cref="INamedTypeSymbol"/> instance to process.</param>
585+
/// <returns>The <see cref="Diagnostic"/> for <paramref name="typeSymbol"/>, if not a valid type.</returns>
586+
public static Diagnostic? GetIsNotifyDataErrorInfoDiagnosticForType(INamedTypeSymbol typeSymbol)
587+
{
588+
// If the containing type is valid, track it
589+
if (!typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator"))
590+
{
591+
return Diagnostic.Create(
592+
InvalidTypeForNotifyDataErrorInfoError,
593+
typeSymbol.Locations.FirstOrDefault(),
594+
typeSymbol);
595+
}
596+
597+
return null;
598+
}
599+
561600
/// <summary>
562601
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args for property changing notifications.
563602
/// </summary>

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,32 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
135135
context.AddSource("__KnownINotifyPropertyChangedArgs.g.cs", compilationUnit.GetText(Encoding.UTF8));
136136
}
137137
});
138+
139+
// Get all class declarations with at least one attribute
140+
IncrementalValuesProvider<INamedTypeSymbol> classSymbols =
141+
context.SyntaxProvider
142+
.CreateSyntaxProvider(
143+
static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 },
144+
static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!);
145+
146+
// Filter only the type symbols with [NotifyRecipients] and create diagnostics for them
147+
IncrementalValuesProvider<Diagnostic> notifyRecipientsErrors =
148+
classSymbols
149+
.Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyRecipientsAttribute"))
150+
.Select(static (item, _) => Execute.GetIsNotifyingRecipientsDiagnosticForType(item))
151+
.Where(static item => item is not null)!;
152+
153+
// Output the diagnostics for [NotifyRecipients]
154+
context.ReportDiagnostics(notifyRecipientsErrors);
155+
156+
// Filter only the type symbols with [NotifyDataErrorInfo] and create diagnostics for them
157+
IncrementalValuesProvider<Diagnostic> notifyDataErrorInfoErrors =
158+
classSymbols
159+
.Where(static item => item.HasAttributeWithFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute"))
160+
.Select(static (item, _) => Execute.GetIsNotifyDataErrorInfoDiagnosticForType(item))
161+
.Where(static item => item is not null)!;
162+
163+
// Output the diagnostics for [NotifyDataErrorInfo]
164+
context.ReportDiagnostics(notifyDataErrorInfoErrors);
138165
}
139166
}

CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,4 +427,36 @@ internal static class DiagnosticDescriptors
427427
isEnabledByDefault: true,
428428
description: "Cannot apply [NotifyDataErrorInfo] to fields that don't have any validation attributes to use during validation.",
429429
helpLinkUri: "https://aka.ms/mvvmtoolkit");
430+
431+
/// <summary>
432+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[NotifyRecipients]</c> is applied to an invalid type.
433+
/// <para>
434+
/// Format: <c>"The type {0} cannot be annotated with [NotifyRecipients], as it doesn't inherit from ObservableRecipient, nor does it use [ObservableRecipient]"</c>.
435+
/// </para>
436+
/// </summary>
437+
public static readonly DiagnosticDescriptor InvalidTypeForNotifyRecipientsError = new DiagnosticDescriptor(
438+
id: "MVVMTK0027",
439+
title: "Invalid type for [NotifyRecipients] attribute",
440+
messageFormat: "The type {0} cannot be annotated with [NotifyRecipients], as it doesn't inherit from ObservableRecipient, nor does it use [ObservableRecipient]",
441+
category: typeof(ObservablePropertyGenerator).FullName,
442+
defaultSeverity: DiagnosticSeverity.Error,
443+
isEnabledByDefault: true,
444+
description: "Types annotated with [NotifyRecipients] must inherit from ObservableRecipient or be annotated with [ObservableRecipient] (including base types).",
445+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
446+
447+
/// <summary>
448+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when <c>[NotifyDataErrorInfo]</c> is applied to an invalid type.
449+
/// <para>
450+
/// Format: <c>"The type {0} cannot be annotated with [NotifyDataErrorInfo], as it doesn't inherit from ObservableValidator"</c>.
451+
/// </para>
452+
/// </summary>
453+
public static readonly DiagnosticDescriptor InvalidTypeForNotifyDataErrorInfoError = new DiagnosticDescriptor(
454+
id: "MVVMTK0028",
455+
title: "Invalid type for [NotifyDataErrorInfo] attribute",
456+
messageFormat: "The type {0} cannot be annotated with [NotifyDataErrorInfo], as it doesn't inherit from ObservableValidator",
457+
category: typeof(ObservablePropertyGenerator).FullName,
458+
defaultSeverity: DiagnosticSeverity.Error,
459+
isEnabledByDefault: true,
460+
description: "Types annotated with [NotifyDataErrorInfo] must inherit from ObservableRecipient.",
461+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
430462
}

0 commit comments

Comments
 (0)