diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs index fcad4998..758d3045 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.Execute.cs @@ -108,20 +108,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray< /// /// Gets the head instance. /// + /// Indicates whether [DynamicallyAccessedMembers] should be generated. /// The head instance with the type attributes. - public static CompilationUnitSyntax GetSyntax() + public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable) { + int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0); + ImmutableArray.Builder attributes = ImmutableArray.CreateBuilder(numberOfAttributes); + + // Prepare the base attributes with are always present: + // + // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] + // [global::System.Diagnostics.DebuggerNonUserCode] + // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + // [global::System.Obsolete("This type is not intended to be used directly by user code")] + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments( + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString()))))))); + attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode"))))); + attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))); + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( + AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never")))))); + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( + AttributeArgument(LiteralExpression( + SyntaxKind.StringLiteralExpression, + Literal("This type is not intended to be used directly by user code"))))))); + + if (isDynamicallyAccessedMembersAttributeAvailable) + { + // Conditionally add the attribute to inform trimming, if the type is available: + // + // [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments( + AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods")))))); + } + // This code produces a compilation unit as follows: // // // // #pragma warning disable // namespace CommunityToolkit.Mvvm.ComponentModel.__Internals // { - // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] - // [global::System.Diagnostics.DebuggerNonUserCode] - // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - // [global::System.Obsolete("This type is not intended to be used directly by user code")] + // // internal static partial class __ObservableValidatorExtensions // { // } @@ -135,21 +171,7 @@ public static CompilationUnitSyntax GetSyntax() Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)) - .AddAttributeLists( - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments( - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).FullName))), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservableValidatorValidateAllPropertiesGenerator).Assembly.GetName().Version.ToString())))))), - AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))), - AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))), - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( - AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))), - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( - AttributeArgument(LiteralExpression( - SyntaxKind.StringLiteralExpression, - Literal("This type is not intended to be used directly by user code"))))))))) + .AddAttributeLists(attributes.MoveToImmutable().ToArray()))) .NormalizeWhitespace(); } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs index 111011e0..965d5fc0 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableValidatorValidateAllPropertiesGenerator.cs @@ -42,10 +42,19 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Collect() .Select(static (item, _) => item.Length > 0); + // Check whether [DynamicallyAccessedMembers] is available + IncrementalValueProvider isDynamicallyAccessedMembersAttributeAvailable = + context.CompilationProvider + .Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")); + + // Gather the conditional flag and attribute availability + IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo = + isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable); + // Generate the header file with the attributes - context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context => + context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) => { - CompilationUnitSyntax compilationUnit = Execute.GetSyntax(); + CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); context.AddSource( hintName: "__ObservableValidatorExtensions.cs", diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs index 2ffc4fa8..b39c074d 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs @@ -65,22 +65,22 @@ public static void FilterWithLanguageVersion( } /// - /// Conditionally invokes - /// if the value produced by the input is . + /// Conditionally invokes + /// if the value produced by the input is , and also supplying a given input state. /// /// The input value being used. /// The source instance. /// The conditional to invoke. - public static void RegisterConditionalImplementationSourceOutput( + public static void RegisterConditionalImplementationSourceOutput( this IncrementalGeneratorInitializationContext context, - IncrementalValueProvider source, - Action action) + IncrementalValueProvider<(bool Condition, T State)> source, + Action action) { - context.RegisterImplementationSourceOutput(source, (context, flag) => + context.RegisterImplementationSourceOutput(source, (context, item) => { - if (flag) + if (item.Condition) { - action(context); + action(context, item.State); } }); } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs b/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs index 3f1d8200..a9c13cab 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.Execute.cs @@ -66,20 +66,56 @@ public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray< /// /// Gets the head instance. /// + /// Indicates whether [DynamicallyAccessedMembers] should be generated. /// The head instance with the type attributes. - public static CompilationUnitSyntax GetSyntax() + public static CompilationUnitSyntax GetSyntax(bool isDynamicallyAccessedMembersAttributeAvailable) { + int numberOfAttributes = 5 + (isDynamicallyAccessedMembersAttributeAvailable ? 1 : 0); + ImmutableArray.Builder attributes = ImmutableArray.CreateBuilder(numberOfAttributes); + + // Prepare the base attributes with are always present: + // + // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] + // [global::System.Diagnostics.DebuggerNonUserCode] + // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] + // [global::System.Obsolete("This type is not intended to be used directly by user code")] + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments( + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))), + AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString()))))))); + attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode"))))); + attributes.Add(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))); + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( + AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never")))))); + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( + AttributeArgument(LiteralExpression( + SyntaxKind.StringLiteralExpression, + Literal("This type is not intended to be used directly by user code"))))))); + + if (isDynamicallyAccessedMembersAttributeAvailable) + { + // Conditionally add the attribute to inform trimming, if the type is available: + // + // [global::System.CodeDom.Compiler.DynamicallyAccessedMembersAttribute(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods)] + attributes.Add( + AttributeList(SingletonSeparatedList( + Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")).AddArgumentListArguments( + AttributeArgument(ParseExpression("global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods")))))); + } + // This code produces a compilation unit as follows: // // // // #pragma warning disable // namespace CommunityToolkit.Mvvm.Messaging.__Internals // { - // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] - // [global::System.Diagnostics.DebuggerNonUserCode] - // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] - // [global::System.Obsolete("This type is not intended to be used directly by user code")] + // // internal static partial class __IMessengerExtensions // { // } @@ -93,21 +129,7 @@ public static CompilationUnitSyntax GetSyntax() Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword)) - .AddAttributeLists( - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")).AddArgumentListArguments( - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).FullName))), - AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(IMessengerRegisterAllGenerator).Assembly.GetName().Version.ToString())))))), - AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))), - AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))), - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( - AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))), - AttributeList(SingletonSeparatedList( - Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( - AttributeArgument(LiteralExpression( - SyntaxKind.StringLiteralExpression, - Literal("This type is not intended to be used directly by user code"))))))))) + .AddAttributeLists(attributes.MoveToImmutable().ToArray()))) .NormalizeWhitespace(); } diff --git a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs b/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs index a84cd4c3..d6de9352 100644 --- a/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs +++ b/CommunityToolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs @@ -57,10 +57,19 @@ item.Symbol.DeclaringSyntaxReferences[0] is SyntaxReference syntaxReference && .Collect() .Select(static (item, _) => item.Length > 0); + // Check whether [DynamicallyAccessedMembers] is available + IncrementalValueProvider isDynamicallyAccessedMembersAttributeAvailable = + context.CompilationProvider + .Select(static (item, _) => item.HasAccessibleTypeWithMetadataName("System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute")); + + // Gather the conditional flag and attribute availability + IncrementalValueProvider<(bool IsHeaderFileNeeded, bool IsDynamicallyAccessedMembersAttributeAvailable)> headerFileInfo = + isHeaderFileNeeded.Combine(isDynamicallyAccessedMembersAttributeAvailable); + // Generate the header file with the attributes - context.RegisterConditionalImplementationSourceOutput(isHeaderFileNeeded, static context => + context.RegisterConditionalImplementationSourceOutput(headerFileInfo, static (context, item) => { - CompilationUnitSyntax compilationUnit = Execute.GetSyntax(); + CompilationUnitSyntax compilationUnit = Execute.GetSyntax(item); context.AddSource( hintName: "__IMessengerExtensions.cs", diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs index 8d55af0a..99c674dc 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableRecipient.cs @@ -3,11 +3,18 @@ // See the LICENSE file in the project root for more information. using System; +#if !NET6_0_OR_GREATER +using System.Collections.Generic; +using System.Linq; +#endif +using System.Reflection; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable CS0618 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] @@ -85,6 +92,23 @@ public void Test_ObservableRecipient_Broadcast(Type type) Assert.AreEqual(message.PropertyName, nameof(SomeRecipient.Data)); } + [TestMethod] + public void Test_IRecipient_VerifyTrimmingAnnotation() + { +#if NET6_0_OR_GREATER + System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute? attribute = + typeof(Messaging.__Internals.__IMessengerExtensions) + .GetCustomAttribute(); + + Assert.IsNotNull(attribute); + Assert.AreEqual(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, attribute.MemberTypes); +#else + IEnumerable attributes = typeof(Messaging.__Internals.__IMessengerExtensions).GetCustomAttributes(); + + Assert.IsFalse(attributes.Any(static a => a.GetType().Name is "DynamicallyAccessedMembersAttribute")); +#endif + } + public class SomeRecipient : ObservableRecipient { public SomeRecipient() diff --git a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableValidator.cs b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableValidator.cs index f10e0483..504e2d88 100644 --- a/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableValidator.cs +++ b/tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservableValidator.cs @@ -12,6 +12,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.VisualStudio.TestTools.UnitTesting; +#pragma warning disable CS0618 + namespace CommunityToolkit.Mvvm.UnitTests; [TestClass] @@ -477,6 +479,23 @@ public void Test_ObservableRecipient_ValidationOnNonValidatableProperties_WithFa validationFunc(viewmodel.GetType())(viewmodel); } + [TestMethod] + public void Test_ObservableValidator_VerifyTrimmingAnnotation() + { +#if NET6_0_OR_GREATER + System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute? attribute = + typeof(ComponentModel.__Internals.__ObservableValidatorExtensions) + .GetCustomAttribute(); + + Assert.IsNotNull(attribute); + Assert.AreEqual(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods, attribute.MemberTypes); +#else + IEnumerable attributes = typeof(ComponentModel.__Internals.__ObservableValidatorExtensions).GetCustomAttributes(); + + Assert.IsFalse(attributes.Any(static a => a.GetType().Name is "DynamicallyAccessedMembersAttribute")); +#endif + } + public class Person : ObservableValidator { private string? name;