// 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.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace CommunityToolkit.Mvvm.SourceGenerators; /// /// A source generator for the ObservableRecipientAttribute type. /// [Generator] public sealed class ObservableRecipientGenerator : TransitiveMembersGenerator { /// /// Initializes a new instance of the class. /// public ObservableRecipientGenerator() : base("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute") { } /// protected override DiagnosticDescriptor TargetTypeErrorDescriptor => ObservableRecipientGeneratorError; /// protected override bool ValidateTargetType( GeneratorExecutionContext context, AttributeData attributeData, ClassDeclarationSyntax classDeclaration, INamedTypeSymbol classDeclarationSymbol, [NotNullWhen(false)] out DiagnosticDescriptor? descriptor) { INamedTypeSymbol observableRecipientSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient")!; INamedTypeSymbol observableObjectSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObject")!; INamedTypeSymbol observableObjectAttributeSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute")!; INamedTypeSymbol iNotifyPropertyChangedSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!; // Check if the type already inherits from ObservableRecipient if (classDeclarationSymbol.InheritsFrom(observableRecipientSymbol)) { descriptor = DuplicateObservableRecipientError; return false; } // In order to use [ObservableRecipient], the target type needs to inherit from ObservableObject, // or be annotated with [ObservableObject] or [INotifyPropertyChanged] (with additional helpers). if (!classDeclarationSymbol.InheritsFrom(observableObjectSymbol) && !classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, observableObjectAttributeSymbol)) && !classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, iNotifyPropertyChangedSymbol) && !a.HasNamedArgument("IncludeAdditionalHelperMethods", false))) { descriptor = MissingBaseObservableObjectFunctionalityError; return false; } descriptor = null; return true; } /// protected override IEnumerable FilterDeclaredMembers( GeneratorExecutionContext context, AttributeData attributeData, ClassDeclarationSyntax classDeclaration, INamedTypeSymbol classDeclarationSymbol, ClassDeclarationSyntax sourceDeclaration) { // If the target type has no constructors, generate constructors as well if (classDeclarationSymbol.InstanceConstructors.Length == 1 && classDeclarationSymbol.InstanceConstructors[0] is { Parameters: { IsEmpty: true }, DeclaringSyntaxReferences: { IsEmpty: true }, IsImplicitlyDeclared: true }) { foreach (ConstructorDeclarationSyntax ctor in sourceDeclaration.Members.OfType()) { string text = ctor.NormalizeWhitespace().ToFullString(); string replaced = text.Replace("ObservableRecipient", classDeclarationSymbol.Name); // Adjust the visibility of the constructors based on whether the target type is abstract. // If that is not the case, the constructors have to be declared as public and not protected. if (!classDeclarationSymbol.IsAbstract) { replaced = replaced.Replace("protected", "public"); } yield return (ConstructorDeclarationSyntax)ParseMemberDeclaration(replaced)!; } } INamedTypeSymbol observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")!; // Skip the SetProperty overloads if the target type inherits from ObservableValidator, to avoid conflicts if (classDeclarationSymbol.InheritsFrom(observableValidatorSymbol)) { foreach (MemberDeclarationSyntax member in sourceDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax)) { if (member is not MethodDeclarationSyntax { Identifier: { ValueText: "SetProperty" } }) { yield return member; } } yield break; } // If the target type has at least one custom constructor, only generate methods foreach (MemberDeclarationSyntax member in sourceDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax)) { yield return member; } } }