// 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;
}
}
}