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