// 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.Generic; using System.Diagnostics.Contracts; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace CommunityToolkit.Mvvm.SourceGenerators.Extensions; /// /// Extension methods for the type. /// internal static class AttributeDataExtensions { /// /// Checks whether a given instance contains a specified named argument. /// /// The type of argument to check. /// The target instance to check. /// The name of the argument to check. /// The expected value for the target named argument. /// Whether or not contains an argument named with the expected value. [Pure] public static bool HasNamedArgument(this AttributeData attributeData, string name, T? value) { foreach (KeyValuePair properties in attributeData.NamedArguments) { if (properties.Key == name) { return properties.Value.Value is T argumentValue && EqualityComparer.Default.Equals(argumentValue, value); } } return false; } /// /// Tries to get a given named argument value from an instance, if present. /// /// The type of argument to check. /// The target instance to check. /// The name of the argument to check. /// The resulting argument value, if present. /// Whether or not contains an argument named with a valid value. public static bool TryGetNamedArgument(this AttributeData attributeData, string name, out T? value) { foreach (KeyValuePair properties in attributeData.NamedArguments) { if (properties.Key == name) { value = (T?)properties.Value.Value; return true; } } value = default; return false; } /// /// Enumerates all items in a flattened sequence of constructor arguments for a given instance. /// /// The type of constructor arguments to retrieve. /// The target instance to get the arguments from. /// A sequence of all constructor arguments of the specified type from . [Pure] public static IEnumerable GetConstructorArguments(this AttributeData attributeData) { static IEnumerable Enumerate(IEnumerable constants) { foreach (TypedConstant constant in constants) { if (constant.IsNull) { continue; } if (constant.Kind == TypedConstantKind.Primitive && constant.Value is T value) { yield return value; } else if (constant.Kind == TypedConstantKind.Array) { foreach (T item in Enumerate(constant.Values)) { yield return item; } } } } return Enumerate(attributeData.ConstructorArguments); } /// /// Creates an node that is equivalent to the input instance. /// /// The input instance to process. /// An replicating the data in . [Pure] public static AttributeSyntax AsAttributeSyntax(this AttributeData attributeData) { IdentifierNameSyntax attributeType = IdentifierName(attributeData.AttributeClass!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); AttributeArgumentSyntax[] arguments = attributeData.ConstructorArguments .Select(static arg => AttributeArgument(ToExpression(arg))).Concat( attributeData.NamedArguments .Select(static arg => AttributeArgument(ToExpression(arg.Value)) .WithNameEquals(NameEquals(IdentifierName(arg.Key))))).ToArray(); return Attribute(attributeType, AttributeArgumentList(SeparatedList(SeparatedList(arguments)))); static ExpressionSyntax ToExpression(TypedConstant arg) { if (arg.IsNull) { return LiteralExpression(SyntaxKind.NullLiteralExpression); } if (arg.Kind == TypedConstantKind.Array) { string elementType = ((IArrayTypeSymbol)arg.Type!).ElementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); return ArrayCreationExpression( ArrayType(IdentifierName(elementType)) .AddRankSpecifiers(ArrayRankSpecifier(SingletonSeparatedList(OmittedArraySizeExpression())))) .WithInitializer(InitializerExpression(SyntaxKind.ArrayInitializerExpression) .AddExpressions(arg.Values.Select(ToExpression).ToArray())); } switch ((arg.Kind, arg.Value)) { case (TypedConstantKind.Primitive, string text): return LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(text)); case (TypedConstantKind.Primitive, bool flag) when flag: return LiteralExpression(SyntaxKind.TrueLiteralExpression); case (TypedConstantKind.Primitive, bool): return LiteralExpression(SyntaxKind.FalseLiteralExpression); case (TypedConstantKind.Primitive, object value): return LiteralExpression(SyntaxKind.NumericLiteralExpression, value switch { byte b => Literal(b), char c => Literal(c), double d => Literal(d), float f => Literal(f), int i => Literal(i), long l => Literal(l), sbyte sb => Literal(sb), short sh => Literal(sh), uint ui => Literal(ui), ulong ul => Literal(ul), ushort ush => Literal(ush), _ => throw new ArgumentException() }); case (TypedConstantKind.Type, ITypeSymbol type): return TypeOfExpression(IdentifierName(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))); case (TypedConstantKind.Enum, object value): return CastExpression( IdentifierName(arg.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)), LiteralExpression(SyntaxKind.NumericLiteralExpression, ParseToken(value.ToString()))); default: throw new ArgumentException(); } } } }