diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md index 739ddfc6..957d6b75 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/AnalyzerReleases.Shipped.md @@ -48,3 +48,4 @@ Rule ID | Category | Severity | Notes MVVMTK0032 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0032 MVVMTK0033 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0033 MVVMTK0034 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0034 +MVVMTK0035 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Error | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0035 diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index d8f44d66..8041977f 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -214,9 +214,17 @@ partial class ObservablePropertyGenerator // lack of IntelliSense when constructing attributes over the field, but this is the best we can do from this end anyway. SymbolInfo attributeSymbolInfo = semanticModel.GetSymbolInfo(attribute, token); + // Check if the attribute type can be resolved, and emit a diagnostic if that is not the case. This happens if eg. the + // attribute type is spelled incorrectly, or if a user is missing the using directive for the attribute type being used. if ((attributeSymbolInfo.Symbol ?? attributeSymbolInfo.CandidateSymbols.SingleOrDefault()) is not ISymbol attributeSymbol || (attributeSymbol as INamedTypeSymbol ?? attributeSymbol.ContainingType) is not INamedTypeSymbol attributeTypeSymbol) { + builder.Add( + InvalidPropertyTargetedAttributeOnObservablePropertyField, + attribute, + fieldSymbol, + attribute.Name); + continue; } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index d96dd7d5..d3c4fd4d 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -541,7 +541,7 @@ internal static class DiagnosticDescriptors description: "Classes with no base types should prefer inheriting from ObservableObject instead of using attributes to generate INotifyPropertyChanged code, as that will " + "reduce the binary size of the application (the attributes are only meant to support cases where the annotated types are already inheriting from a different type).", - helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0032"); + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0033"); /// /// Gets a indicating when a field with [ObservableProperty] is being directly referenced. @@ -557,5 +557,21 @@ internal static class DiagnosticDescriptors defaultSeverity: DiagnosticSeverity.Warning, isEnabledByDefault: true, description: "Fields with [ObservableProperty] should not be directly referenced, and the generated properties should be used instead.", - helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0033"); + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0034"); + + /// + /// Gets a indicating when a field with [ObservableProperty] is using an invalid attribute targeting the property. + /// + /// Format: "The field {0} annotated with [ObservableProperty] is using attribute "{1}" which was not recognized as a valid type (are you missing a using directive?)". + /// + /// + public static readonly DiagnosticDescriptor InvalidPropertyTargetedAttributeOnObservablePropertyField = new DiagnosticDescriptor( + id: "MVVMTK0035", + title: "Invalid property targeted attribute type", + messageFormat: "The field {0} annotated with [ObservableProperty] is using attribute \"{1}\" which was not recognized as a valid type (are you missing a using directive?)", + category: typeof(ObservablePropertyGenerator).FullName, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "All attributes targeting the generated property for a field annotated with [ObservableProperty] must correctly be resolved to valid types.", + helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0035"); } diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index a0641bad..a2cd11c3 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -1625,6 +1625,56 @@ public class Test_SourceGeneratorsDiagnostics await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } + // See https://github.com/CommunityToolkit/dotnet/issues/563 + [TestMethod] + public void InvalidPropertyTargetedAttributeOnObservablePropertyField_MissingUsingDirective() + { + string source = """ + using System; + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + [property: MyTest] + public int number; + } + } + + namespace MyAttributes + { + [AttributeUsage(AttributeTargets.Property)] + public class MyTestAttribute : Attribute + { + } + } + """; + + VerifyGeneratedDiagnostics(source, "MVVMTK0035"); + } + + [TestMethod] + public void InvalidPropertyTargetedAttributeOnObservablePropertyField_TypoInAttributeName() + { + string source = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp + { + public partial class MyViewModel : ObservableObject + { + [ObservableProperty] + [property: Fbuifbweif] + public int number; + } + } + """; + + VerifyGeneratedDiagnostics(source, "MVVMTK0035"); + } + /// /// Verifies the diagnostic errors for a given analyzer, and that all available source generators can run successfully with the input source (including subsequent compilation). ///