From 7c9958cb85d53ef057bff1e8160632df8d767328 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 22 Jan 2023 21:28:43 +0100 Subject: [PATCH 1/5] Add InvalidTypeForNotifyPropertyChangedRecipientsError --- ...ityToolkit.Mvvm.SourceGenerators.projitems | 1 + .../ObservablePropertyGenerator.Execute.cs | 20 ------- .../ObservablePropertyGenerator.cs | 10 ---- ...pertyChangedRecipientsAttributeAnalyzer.cs | 54 +++++++++++++++++++ 4 files changed, 55 insertions(+), 30 deletions(-) create mode 100644 src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems index 405e84cfd..88d743ae8 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems @@ -39,6 +39,7 @@ + diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 8041977f0..90758a9c4 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -555,26 +555,6 @@ private static bool TryGetIsNotifyingRecipients( return false; } - /// - /// Checks whether a given type using [NotifyPropertyChangedRecipients] is valid and creates a if not. - /// - /// The input instance to process. - /// The for , if not a valid type. - public static Diagnostic? GetIsNotifyingRecipientsDiagnosticForType(INamedTypeSymbol typeSymbol) - { - // If the containing type is valid, track it - if (!typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") && - !typeSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) - { - return Diagnostic.Create( - InvalidTypeForNotifyPropertyChangedRecipientsError, - typeSymbol.Locations.FirstOrDefault(), - typeSymbol); - } - - return null; - } - /// /// Checks whether a given generated property should also validate its value. /// diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs index 6dd813276..2c11dfa20 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs @@ -121,16 +121,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }, static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); - // Filter only the type symbols with [NotifyPropertyChangedRecipients] and create diagnostics for them - IncrementalValuesProvider notifyRecipientsErrors = - classSymbols - .Where(static item => item.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) - .Select(static (item, _) => Execute.GetIsNotifyingRecipientsDiagnosticForType(item)) - .Where(static item => item is not null)!; - - // Output the diagnostics for [NotifyPropertyChangedRecipients] - context.ReportDiagnostics(notifyRecipientsErrors); - // Filter only the type symbols with [NotifyDataErrorInfo] and create diagnostics for them IncrementalValuesProvider notifyDataErrorInfoErrors = classSymbols diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs new file mode 100644 index 000000000..e96ed2de4 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates an error when a class level [NotifyPropertyChangedRecipients] use is detected. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidTypeForNotifyPropertyChangedRecipientsError); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterSymbolAction(static context => + { + // We're looking for all class declarations + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) + { + return; + } + + // Only inspect class that are using [NotifyPropertyChangedRecipients] + if (!classSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) + { + return; + } + + // If the containing type is not valid, emit a diagnostic + if (!classSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") && + !classSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidTypeForNotifyPropertyChangedRecipientsError, + classSymbol.Locations.FirstOrDefault(), + classSymbol)); + } + }, SymbolKind.NamedType); + } +} From 1b02376532516fa8e2bf77df6c8276f4f4f9b6ce Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 22 Jan 2023 21:30:39 +0100 Subject: [PATCH 2/5] Add InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer --- ...ityToolkit.Mvvm.SourceGenerators.projitems | 1 + .../ObservablePropertyGenerator.Execute.cs | 19 ------- .../ObservablePropertyGenerator.cs | 17 ------ ...velNotifyDataErrorInfoAttributeAnalyzer.cs | 53 +++++++++++++++++++ ...pertyChangedRecipientsAttributeAnalyzer.cs | 2 +- 5 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems index 88d743ae8..91866191c 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems @@ -39,6 +39,7 @@ + diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs index 90758a9c4..031a9d473 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs @@ -637,25 +637,6 @@ private static bool TryGetNotifyDataErrorInfo( return false; } - /// - /// Checks whether a given type using [NotifyDataErrorInfo] is valid and creates a if not. - /// - /// The input instance to process. - /// The for , if not a valid type. - public static Diagnostic? GetIsNotifyDataErrorInfoDiagnosticForType(INamedTypeSymbol typeSymbol) - { - // If the containing type is valid, track it - if (!typeSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) - { - return Diagnostic.Create( - InvalidTypeForNotifyDataErrorInfoError, - typeSymbol.Locations.FirstOrDefault(), - typeSymbol); - } - - return null; - } - /// /// Gets a instance with the cached args for property changing notifications. /// diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs index 2c11dfa20..e97cfed29 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.cs @@ -113,22 +113,5 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.AddSource("__KnownINotifyPropertyChangedArgs.g.cs", compilationUnit.GetText(Encoding.UTF8)); } }); - - // Get all class declarations with at least one attribute - IncrementalValuesProvider classSymbols = - context.SyntaxProvider - .CreateSyntaxProvider( - static (node, _) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }, - static (context, _) => (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(context.Node)!); - - // Filter only the type symbols with [NotifyDataErrorInfo] and create diagnostics for them - IncrementalValuesProvider notifyDataErrorInfoErrors = - classSymbols - .Where(static item => item.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) - .Select(static (item, _) => Execute.GetIsNotifyDataErrorInfoDiagnosticForType(item)) - .Where(static item => item is not null)!; - - // Output the diagnostics for [NotifyDataErrorInfo] - context.ReportDiagnostics(notifyDataErrorInfoErrors); } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs new file mode 100644 index 000000000..8fa67f723 --- /dev/null +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Linq; +using CommunityToolkit.Mvvm.SourceGenerators.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; + +namespace CommunityToolkit.Mvvm.SourceGenerators; + +/// +/// A diagnostic analyzer that generates an error when a class level [NotifyDataErrorInfo] use is detected. +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidTypeForNotifyDataErrorInfoError); + + /// + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); + context.EnableConcurrentExecution(); + + context.RegisterSymbolAction(static context => + { + // We're looking for all class declarations + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) + { + return; + } + + // Only inspect classes that are using [NotifyDataErrorInfo] + if (!classSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) + { + return; + } + + // If the containing type is not valid, emit a diagnostic + if (!classSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidTypeForNotifyDataErrorInfoError, + classSymbol.Locations.FirstOrDefault(), + classSymbol)); + } + }, SymbolKind.NamedType); + } +} diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs index e96ed2de4..e33605dc6 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs @@ -34,7 +34,7 @@ public override void Initialize(AnalysisContext context) return; } - // Only inspect class that are using [NotifyPropertyChangedRecipients] + // Only inspect classes that are using [NotifyPropertyChangedRecipients] if (!classSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) { return; From b7f121c2ef9771ba977ab304bd0110612fbe6b3d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sun, 22 Jan 2023 21:34:37 +0100 Subject: [PATCH 3/5] Update unit tests for the two new analyzers --- .../Test_SourceGeneratorsDiagnostics.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs index a2cd11c3e..6e15ce95e 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs @@ -1337,7 +1337,7 @@ public partial class SampleViewModel : ObservableValidator } [TestMethod] - public void InvalidTypeForNotifyPropertyChangedRecipientsError() + public async Task InvalidTypeForNotifyPropertyChangedRecipientsError() { string source = """ using CommunityToolkit.Mvvm.ComponentModel; @@ -1345,7 +1345,7 @@ public void InvalidTypeForNotifyPropertyChangedRecipientsError() namespace MyApp { [NotifyPropertyChangedRecipients] - public partial class MyViewModel : ObservableObject + public partial class {|MVVMTK0027:MyViewModel|} : ObservableObject { [ObservableProperty] public int number; @@ -1353,11 +1353,11 @@ public partial class MyViewModel : ObservableObject } """; - VerifyGeneratedDiagnostics(source, "MVVMTK0027"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] - public void InvalidTypeForNotifyDataErrorInfoError() + public async Task InvalidTypeForNotifyDataErrorInfoError() { string source = """ using System.ComponentModel.DataAnnotations; @@ -1366,16 +1366,13 @@ public void InvalidTypeForNotifyDataErrorInfoError() namespace MyApp { [NotifyDataErrorInfo] - public partial class SampleViewModel : ObservableObject + public partial class {|MVVMTK0028:SampleViewModel|} : ObservableObject { - [ObservableProperty] - [Required] - private string name; } } """; - VerifyGeneratedDiagnostics(source, "MVVMTK0006", "MVVMTK0028"); + await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration(source, LanguageVersion.CSharp8); } [TestMethod] From c70e9734785ee3cf36da30e2f38a36c86a2c1cb1 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 23 Jan 2023 14:42:48 +0100 Subject: [PATCH 4/5] Use type symbols for lookup in new analyzers --- ...velNotifyDataErrorInfoAttributeAnalyzer.cs | 6 ++- ...pertyChangedRecipientsAttributeAnalyzer.cs | 9 ++-- .../Extensions/ISymbolExtensions.cs | 28 +++++++++--- .../Extensions/ITypeSymbolExtensions.cs | 44 ++++++++++++++++++- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs index 8fa67f723..2596aa5ed 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs @@ -35,13 +35,15 @@ public override void Initialize(AnalysisContext context) } // Only inspect classes that are using [NotifyDataErrorInfo] - if (!classSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute")) + if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") is not INamedTypeSymbol notifyDataErrorInfoAttributeSymbol || + !classSymbol.HasAttributeWithType(notifyDataErrorInfoAttributeSymbol)) { return; } // If the containing type is not valid, emit a diagnostic - if (!classSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")) + if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator") is not INamedTypeSymbol observableValidatorSymbol || + !classSymbol.InheritsFromType(observableValidatorSymbol)) { context.ReportDiagnostic(Diagnostic.Create( InvalidTypeForNotifyDataErrorInfoError, diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs index e33605dc6..c8c0e4385 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs @@ -35,14 +35,17 @@ public override void Initialize(AnalysisContext context) } // Only inspect classes that are using [NotifyPropertyChangedRecipients] - if (!classSymbol.HasAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute")) + if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") is not INamedTypeSymbol notifyPropertyChangedRecipientsAttributeSymbol || + !classSymbol.HasAttributeWithType(notifyPropertyChangedRecipientsAttributeSymbol)) { return; } // If the containing type is not valid, emit a diagnostic - if (!classSymbol.InheritsFromFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") && - !classSymbol.HasOrInheritsAttributeWithFullyQualifiedMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")) + if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") is INamedTypeSymbol observableRecipientSymbol && + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") is INamedTypeSymbol observableRecipientAttributeSymbol && + !classSymbol.InheritsFromType(observableRecipientSymbol) && + !classSymbol.HasOrInheritsAttributeWithType(observableRecipientAttributeSymbol)) { context.ReportDiagnostic(Diagnostic.Create( InvalidTypeForNotifyPropertyChangedRecipientsError, diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs index a54073fd1..a0fa4d315 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ISymbolExtensions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; #if !ROSLYN_4_3_1_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif @@ -54,9 +53,7 @@ public static bool HasFullyQualifiedName(this ISymbol symbol, string name) /// Whether or not has an attribute with the specified name. public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name) { - ImmutableArray attributes = symbol.GetAttributes(); - - foreach (AttributeData attribute in attributes) + foreach (AttributeData attribute in symbol.GetAttributes()) { if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true) { @@ -67,6 +64,25 @@ public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbo return false; } + /// + /// Checks whether or not a given symbol has an attribute with the specified fully qualified metadata name. + /// + /// The input instance to check. + /// The instance for the attribute type to look for. + /// Whether or not has an attribute with the specified type. + public static bool HasAttributeWithType(this ISymbol symbol, ITypeSymbol typeSymbol) + { + foreach (AttributeData attribute in symbol.GetAttributes()) + { + if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, typeSymbol)) + { + return true; + } + } + + return false; + } + #if !ROSLYN_4_3_1_OR_GREATER /// /// Tries to get an attribute with the specified fully qualified metadata name. @@ -77,9 +93,7 @@ public static bool HasAttributeWithFullyQualifiedMetadataName(this ISymbol symbo /// Whether or not has an attribute with the specified name. public static bool TryGetAttributeWithFullyQualifiedMetadataName(this ISymbol symbol, string name, [NotNullWhen(true)] out AttributeData? attributeData) { - ImmutableArray attributes = symbol.GetAttributes(); - - foreach (AttributeData attribute in attributes) + foreach (AttributeData attribute in symbol.GetAttributes()) { if (attribute.AttributeClass?.HasFullyQualifiedMetadataName(name) == true) { diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs index 52a813986..6e976501b 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Extensions/ITypeSymbolExtensions.cs @@ -43,7 +43,7 @@ public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeS { INamedTypeSymbol? baseType = typeSymbol.BaseType; - while (baseType != null) + while (baseType is not null) { if (baseType.HasFullyQualifiedMetadataName(name)) { @@ -56,6 +56,29 @@ public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeS return false; } + /// + /// Checks whether or not a given inherits from a specified type. + /// + /// The target instance to check. + /// The instane to check for inheritance from. + /// Whether or not inherits from . + public static bool InheritsFromType(this ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol) + { + INamedTypeSymbol? currentBaseTypeSymbol = typeSymbol.BaseType; + + while (currentBaseTypeSymbol is not null) + { + if (SymbolEqualityComparer.Default.Equals(currentBaseTypeSymbol, baseTypeSymbol)) + { + return true; + } + + currentBaseTypeSymbol = currentBaseTypeSymbol.BaseType; + } + + return false; + } + /// /// Checks whether or not a given implements an interface with a specified name. /// @@ -113,6 +136,25 @@ public static bool HasOrInheritsAttributeWithFullyQualifiedMetadataName(this ITy return false; } + /// + /// Checks whether or not a given has or inherits a specified attribute. + /// + /// The target instance to check. + /// The instane to check for inheritance from. + /// Whether or not has or inherits an attribute of type . + public static bool HasOrInheritsAttributeWithType(this ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol) + { + for (ITypeSymbol? currentType = typeSymbol; currentType is not null; currentType = currentType.BaseType) + { + if (currentType.HasAttributeWithType(baseTypeSymbol)) + { + return true; + } + } + + return false; + } + /// /// Checks whether or not a given inherits a specified attribute. /// If the type has no base type, this method will automatically handle that and return . From 1690599cdcdfad949c6972bd8f9a69b067af184d Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 23 Jan 2023 15:41:18 +0100 Subject: [PATCH 5/5] Gather all symbols in RegisterCompilationStartAction in new analyzers --- ...velNotifyDataErrorInfoAttributeAnalyzer.cs | 39 +++++++++-------- ...pertyChangedRecipientsAttributeAnalyzer.cs | 43 ++++++++++--------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs index 2596aa5ed..f4232ada0 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer.cs @@ -26,30 +26,33 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); - context.RegisterSymbolAction(static context => + context.RegisterCompilationStartAction(static context => { - // We're looking for all class declarations - if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) - { - return; - } - - // Only inspect classes that are using [NotifyDataErrorInfo] + // Get the symbols for [NotifyDataErrorInfo] and ObservableValidator if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyDataErrorInfoAttribute") is not INamedTypeSymbol notifyDataErrorInfoAttributeSymbol || - !classSymbol.HasAttributeWithType(notifyDataErrorInfoAttributeSymbol)) + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator") is not INamedTypeSymbol observableValidatorSymbol) { return; } - // If the containing type is not valid, emit a diagnostic - if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator") is not INamedTypeSymbol observableValidatorSymbol || - !classSymbol.InheritsFromType(observableValidatorSymbol)) + context.RegisterSymbolAction(context => { - context.ReportDiagnostic(Diagnostic.Create( - InvalidTypeForNotifyDataErrorInfoError, - classSymbol.Locations.FirstOrDefault(), - classSymbol)); - } - }, SymbolKind.NamedType); + // We're looking for all class declarations + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) + { + return; + } + + // Emit a diagnostic for types that use [NotifyDataErrorInfo] but don't inherit from ObservableValidator + if (classSymbol.HasAttributeWithType(notifyDataErrorInfoAttributeSymbol) && + !classSymbol.InheritsFromType(observableValidatorSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidTypeForNotifyDataErrorInfoError, + classSymbol.Locations.FirstOrDefault(), + classSymbol)); + } + }, SymbolKind.NamedType); + }); } } diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs index c8c0e4385..d1062ba42 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer.cs @@ -26,32 +26,35 @@ public override void Initialize(AnalysisContext context) context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); context.EnableConcurrentExecution(); - context.RegisterSymbolAction(static context => + context.RegisterCompilationStartAction(static context => { - // We're looking for all class declarations - if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) - { - return; - } - - // Only inspect classes that are using [NotifyPropertyChangedRecipients] + // Get the symbols for [NotifyPropertyChangedRecipients], ObservableRecipient and [ObservableRecipient] if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.NotifyPropertyChangedRecipientsAttribute") is not INamedTypeSymbol notifyPropertyChangedRecipientsAttributeSymbol || - !classSymbol.HasAttributeWithType(notifyPropertyChangedRecipientsAttributeSymbol)) + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") is not INamedTypeSymbol observableRecipientSymbol || + context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") is not INamedTypeSymbol observableRecipientAttributeSymbol) { return; } - // If the containing type is not valid, emit a diagnostic - if (context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient") is INamedTypeSymbol observableRecipientSymbol && - context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") is INamedTypeSymbol observableRecipientAttributeSymbol && - !classSymbol.InheritsFromType(observableRecipientSymbol) && - !classSymbol.HasOrInheritsAttributeWithType(observableRecipientAttributeSymbol)) + context.RegisterSymbolAction(context => { - context.ReportDiagnostic(Diagnostic.Create( - InvalidTypeForNotifyPropertyChangedRecipientsError, - classSymbol.Locations.FirstOrDefault(), - classSymbol)); - } - }, SymbolKind.NamedType); + // We're looking for all class declarations + if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class, IsImplicitlyDeclared: false } classSymbol) + { + return; + } + + // Emit a diagnstic for types that use [NotifyPropertyChangedRecipients] but are neither inheriting from ObservableRecipient nor using [ObservableRecipient] + if (classSymbol.HasAttributeWithType(notifyPropertyChangedRecipientsAttributeSymbol) && + !classSymbol.InheritsFromType(observableRecipientSymbol) && + !classSymbol.HasOrInheritsAttributeWithType(observableRecipientAttributeSymbol)) + { + context.ReportDiagnostic(Diagnostic.Create( + InvalidTypeForNotifyPropertyChangedRecipientsError, + classSymbol.Locations.FirstOrDefault(), + classSymbol)); + } + }, SymbolKind.NamedType); + }); } }