// 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.Linq;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CommunityToolkit.Mvvm.SourceGenerators;
///
partial class ObservableValidatorValidateAllPropertiesGenerator
{
///
/// A container for all the logic for .
///
private static class Execute
{
///
/// Checks whether a given type inherits from ObservableValidator.
///
/// The input instance to check.
/// Whether inherits from ObservableValidator.
public static bool IsObservableValidator(INamedTypeSymbol typeSymbol)
{
return typeSymbol.InheritsFromFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
}
///
/// Gets the instance from an input symbol.
///
/// The input instance to inspect.
/// The resulting instance for .
public static ValidationInfo GetInfo(INamedTypeSymbol typeSymbol)
{
ImmutableArray.Builder propertyNames = ImmutableArray.CreateBuilder();
foreach (ISymbol memberSymbol in typeSymbol.GetMembers())
{
if (memberSymbol is { IsStatic: true } or not (IPropertySymbol { IsIndexer: false } or IFieldSymbol))
{
continue;
}
ImmutableArray attributes = memberSymbol.GetAttributes();
// Also include fields that are annotated with [ObservableProperty]. This is necessary because
// all generators run in an undefined order and looking at the same original compilation, so the
// current one wouldn't be able to see generated properties from other generators directly.
if (memberSymbol is IFieldSymbol &&
!attributes.Any(static a => a.AttributeClass?.HasFullyQualifiedName(
"global::CommunityToolkit.Mvvm.ComponentModel.ObservablePropertyAttribute") == true))
{
continue;
}
// Skip the current member if there are no validation attributes applied to it
if (!attributes.Any(a => a.AttributeClass?.InheritsFromFullyQualifiedName(
"global::System.ComponentModel.DataAnnotations.ValidationAttribute") == true))
{
continue;
}
// Get the target property name either directly or matching the generated one
string propertyName = memberSymbol switch
{
IPropertySymbol propertySymbol => propertySymbol.Name,
IFieldSymbol fieldSymbol => ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol),
_ => throw new InvalidOperationException("Invalid symbol type")
};
propertyNames.Add(propertyName);
}
return new(
typeSymbol.GetFullMetadataNameForFileName(),
typeSymbol.GetFullyQualifiedName(),
propertyNames.ToImmutable());
}
///
/// Gets the instance from the given info.
///
/// The type symbol for the target type being inspected.
/// The input array of interface type symbols being handled.
/// A instance for the current type being inspected.
public static RecipientInfo GetInfo(INamedTypeSymbol typeSymbol, ImmutableArray interfaceSymbols)
{
ImmutableArray.Builder names = ImmutableArray.CreateBuilder(interfaceSymbols.Length);
foreach (INamedTypeSymbol interfaceSymbol in interfaceSymbols)
{
names.Add(interfaceSymbol.TypeArguments[0].GetFullyQualifiedName());
}
return new(
typeSymbol.GetFullMetadataNameForFileName(),
typeSymbol.GetFullyQualifiedName(),
names.MoveToImmutable());
}
///
/// Gets the head instance.
///
/// Indicates whether [DynamicallyAccessedMembers] should be generated.
/// The head instance with the type attributes.
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
// {
//
// internal static partial class __ObservableValidatorExtensions
// {
// }
// }
return
CompilationUnit().AddMembers(
NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.ComponentModel.__Internals")).WithLeadingTrivia(TriviaList(
Comment("// "),
Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)))).AddMembers(
ClassDeclaration("__ObservableValidatorExtensions").AddModifiers(
Token(SyntaxKind.InternalKeyword),
Token(SyntaxKind.StaticKeyword),
Token(SyntaxKind.PartialKeyword))
.AddAttributeLists(attributes.MoveToImmutable().ToArray())))
.NormalizeWhitespace();
}
///
/// Gets the instance for the input recipient.
///
/// The input instance to process.
/// The generated instance for .
public static CompilationUnitSyntax GetSyntax(ValidationInfo validationInfo)
{
// Create a static factory method creating a delegate that can be used to validate all properties in a given class.
// This pattern is used so that the library doesn't have to use MakeGenericType(...) at runtime, nor use unsafe casts
// over the created delegate to be able to cache it as an Action