Skip to content

Commit 25cf030

Browse files
committed
Added diagnostic for observable property + validation without base class
1 parent 6188d3d commit 25cf030

File tree

5 files changed

+51
-12
lines changed

5 files changed

+51
-12
lines changed

Microsoft.Toolkit.Mvvm.SourceGenerators/AnalyzerReleases.Unshipped.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ MVVMTK0004 | Microsoft.Toolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGener
1212
MVVMTK0005 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Error | See https://aka.ms/mvvmtoolkit
1313
MVVMTK0006 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Error | See https://aka.ms/mvvmtoolkit
1414
MVVMTK0007 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit
15-
MVVMTK0008 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit
15+
MVVMTK0008 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservableRecipientGenerator | Error | See https://aka.ms/mvvmtoolkit
16+
MVVMTK0009 | Microsoft.Toolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit

Microsoft.Toolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
using Microsoft.CodeAnalysis.CSharp.Syntax;
1313
using Microsoft.CodeAnalysis.Text;
1414
using Microsoft.Toolkit.Mvvm.ComponentModel;
15+
using Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics;
1516
using Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions;
1617
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1718
using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle;
19+
using static Microsoft.Toolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
1820

1921
namespace Microsoft.Toolkit.Mvvm.SourceGenerators
2022
{
@@ -63,16 +65,19 @@ private static void OnExecute(
6365
INamedTypeSymbol classDeclarationSymbol,
6466
IEnumerable<SyntaxReceiver.Item> items)
6567
{
66-
// Check whether INotifyPropertyChanging is present as well
6768
INamedTypeSymbol
6869
iNotifyPropertyChangingSymbol = context.Compilation.GetTypeByMetadataName(typeof(INotifyPropertyChanging).FullName)!,
6970
observableObjectSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableObject")!,
70-
observableObjectAttributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(ObservableObjectAttribute).FullName)!;
71+
observableObjectAttributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(ObservableObjectAttribute).FullName)!,
72+
observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator")!;
7173

72-
bool isNotifyPropertyChanging =
73-
classDeclarationSymbol.AllInterfaces.Contains(iNotifyPropertyChangingSymbol, SymbolEqualityComparer.Default) ||
74-
classDeclarationSymbol.InheritsFrom(observableObjectSymbol) ||
75-
classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, observableObjectAttributeSymbol));
74+
// Check whether the current type implements INotifyPropertyChanging and whether it inherits from ObservableValidator
75+
bool
76+
isObservableValidator = classDeclarationSymbol.InheritsFrom(observableValidatorSymbol),
77+
isNotifyPropertyChanging =
78+
classDeclarationSymbol.AllInterfaces.Contains(iNotifyPropertyChangingSymbol, SymbolEqualityComparer.Default) ||
79+
classDeclarationSymbol.InheritsFrom(observableObjectSymbol) ||
80+
classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, observableObjectAttributeSymbol));
7681

7782
// Create the class declaration for the user type. This will produce a tree as follows:
7883
//
@@ -83,7 +88,7 @@ private static void OnExecute(
8388
var classDeclarationSyntax =
8489
ClassDeclaration(classDeclarationSymbol.Name)
8590
.WithModifiers(classDeclaration.Modifiers)
86-
.AddMembers(items.Select(item => CreatePropertyDeclaration(context, item.LeadingTrivia, item.FieldSymbol, isNotifyPropertyChanging)).ToArray());
91+
.AddMembers(items.Select(item => CreatePropertyDeclaration(context, item.LeadingTrivia, item.FieldSymbol, isNotifyPropertyChanging, isObservableValidator)).ToArray());
8792

8893
TypeDeclarationSyntax typeDeclarationSyntax = classDeclarationSyntax;
8994

@@ -125,16 +130,21 @@ private static void OnExecute(
125130
/// <param name="leadingTrivia">The leading trivia for the field to process.</param>
126131
/// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
127132
/// <param name="isNotifyPropertyChanging">Indicates whether or not <see cref="INotifyPropertyChanging"/> is also implemented.</param>
133+
/// <param name="isObservableValidator">Indicates whether or not the containing type inherits from <c>ObservableValidator</c>.</param>
128134
/// <returns>A generated <see cref="PropertyDeclarationSyntax"/> instance for the input field.</returns>
129135
[Pure]
130-
private static PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExecutionContext context, SyntaxTriviaList leadingTrivia, IFieldSymbol fieldSymbol, bool isNotifyPropertyChanging)
136+
private static PropertyDeclarationSyntax CreatePropertyDeclaration(
137+
GeneratorExecutionContext context,
138+
SyntaxTriviaList leadingTrivia,
139+
IFieldSymbol fieldSymbol,
140+
bool isNotifyPropertyChanging,
141+
bool isObservableValidator)
131142
{
132143
// Get the field type and the target property name
133144
string
134145
typeName = fieldSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
135146
propertyName = GetGeneratedPropertyName(fieldSymbol);
136147

137-
INamedTypeSymbol observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator")!;
138148
INamedTypeSymbol alsoNotifyForAttributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(AlsoNotifyForAttribute).FullName)!;
139149
INamedTypeSymbol? validationAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute");
140150

@@ -190,6 +200,19 @@ private static PropertyDeclarationSyntax CreatePropertyDeclaration(GeneratorExec
190200

191201
if (validationAttributes.Count > 0)
192202
{
203+
// Emit a diagnostic if the current type doesn't inherit from ObservableValidator
204+
if (!isObservableValidator)
205+
{
206+
context.ReportDiagnostic(
207+
MissingObservableValidatorInheritanceError,
208+
fieldSymbol,
209+
fieldSymbol.ContainingType,
210+
fieldSymbol.Name,
211+
validationAttributes.Count);
212+
213+
setterBlock = Block();
214+
}
215+
193216
// Generate the inner setter block as follows:
194217
//
195218
// SetProperty(ref <FIELD_NAME>, value, true);

Microsoft.Toolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6-
using System.CodeDom.Compiler;
76
using System.Collections.Generic;
87
using System.Diagnostics.CodeAnalysis;
98
using System.Diagnostics.Contracts;

Microsoft.Toolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,21 @@ internal static class DiagnosticDescriptors
140140
isEnabledByDefault: true,
141141
description: $"Cannot apply [{nameof(ObservableRecipientAttribute)}] to a type that lacks necessary base functionality (it should either inherit from ObservableObject, or be annotated with [{nameof(ObservableObjectAttribute)}] or [{nameof(INotifyPropertyChangedAttribute)}]).",
142142
helpLinkUri: "https://aka.ms/mvvmtoolkit");
143+
144+
/// <summary>
145+
/// Gets a <see cref="DiagnosticDescriptor"/> indicating when the target type doesn't inherit from the <c>ObservableValidator</c> class.
146+
/// <para>
147+
/// Format: <c>"The field {0}.{1} cannot be used to generate an observable property, as it has {2} validation attribute(s) but is declared in a type that doesn't inherit from ObservableValidator"</c>.
148+
/// </para>
149+
/// </summary>
150+
public static readonly DiagnosticDescriptor MissingObservableValidatorInheritanceError = new(
151+
id: "MVVMTK0009",
152+
title: "Missing ObservableValidator inheritance",
153+
messageFormat: $"The field {{0}}.{{1}} cannot be used to generate an observable property, as it has {{2}} validation attribute(s) but is declared in a type that doesn't inherit from ObservableValidator",
154+
category: typeof(ObservablePropertyGenerator).FullName,
155+
defaultSeverity: DiagnosticSeverity.Error,
156+
isEnabledByDefault: true,
157+
description: $"Cannot apply [{nameof(ObservablePropertyAttribute)}] to fields with validation attributes if they are declared in a type that doesn't inherit from ObservableValidator.",
158+
helpLinkUri: "https://aka.ms/mvvmtoolkit");
143159
}
144160
}

UnitTests/UnitTests.NetCore/Mvvm/Test_ObservablePropertyAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public sealed partial class DependentPropertyModel
138138
public string FullName => $"{Name} {Surname}";
139139
}
140140

141-
public partial class MyFormViewModel : ObservableRecipient
141+
public partial class MyFormViewModel : ObservableValidator
142142
{
143143
[ObservableProperty]
144144
[Required]

0 commit comments

Comments
 (0)