// 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; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace CommunityToolkit.Mvvm.SourceGenerators; /// partial class TransitiveMembersGenerator { /// /// A container for all the logic for . /// internal static class Execute { /// /// Loads the source instance to get member declarations from. /// /// The fully qualified name of the attribute type to look for. /// The source instance to get member declarations from. public static ClassDeclarationSyntax LoadClassDeclaration(string attributeType) { string attributeTypeName = attributeType.Split('.').Last(); string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{attributeTypeName.Replace("Attribute", string.Empty)}.cs"; using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename); using StreamReader reader = new(stream); string observableObjectSource = reader.ReadToEnd(); SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(observableObjectSource); return syntaxTree.GetRoot().DescendantNodes().OfType().First(); } /// /// Processes the sequence of member declarations to generate. /// /// The type of generator being used. /// The input sequence of member declarations to generate. /// The resulting sequence of member declarations for sealed types. /// The resulting sequence of member declarations for non sealed types. public static void ProcessMemberDeclarations( Type generatorType, ImmutableArray memberDeclarations, out ImmutableArray sealedMemberDeclarations, out ImmutableArray nonSealedMemberDeclarations) { ImmutableArray annotatedMemberDeclarations = memberDeclarations.Select(member => { // [GeneratedCode] is always present member = member .WithoutLeadingTrivia() .AddAttributeLists(AttributeList(SingletonSeparatedList( Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")) .AddArgumentListArguments( AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(generatorType.FullName))), AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(generatorType.Assembly.GetName().Version.ToString()))))))) .WithLeadingTrivia(member.GetLeadingTrivia()); // [ExcludeFromCodeCoverage] is not supported on interfaces and fields if (member.Kind() is not SyntaxKind.InterfaceDeclaration and not SyntaxKind.FieldDeclaration) { member = member.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))); } return member; }).ToImmutableArray(); // If the target class is sealed, make protected members private and remove the virtual modifier sealedMemberDeclarations = annotatedMemberDeclarations.Select(static member => { // Constructors become public for sealed types if (member is ConstructorDeclarationSyntax) { return member.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PublicKeyword); } // Other members become private return member .ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword) .RemoveModifier(SyntaxKind.VirtualKeyword); }).ToImmutableArray(); nonSealedMemberDeclarations = annotatedMemberDeclarations; } } }