mirror of
https://github.com/CommunityToolkit/dotnet.git
synced 2026-10-20 06:07:37 +08:00
Added initial support for generated validation property attributes
This commit is contained in:
+22
-10
@@ -165,22 +165,32 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
|
||||
IdentifierName("value"))),
|
||||
ExpressionStatement(InvocationExpression(IdentifierName("OnPropertyChanged"))));
|
||||
|
||||
INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(AlsoNotifyForAttribute).FullName)!;
|
||||
INamedTypeSymbol alsoNotifyForAttributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(AlsoNotifyForAttribute).FullName)!;
|
||||
INamedTypeSymbol? validationAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute");
|
||||
|
||||
// Add dependent property notifications, if needed
|
||||
if (fieldSymbol.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeSymbol)) is AttributeData attributeData &&
|
||||
attributeData.ConstructorArguments.Length == 1)
|
||||
List<AttributeSyntax> validationAttributes = new();
|
||||
|
||||
foreach (AttributeData attributeData in fieldSymbol.GetAttributes())
|
||||
{
|
||||
foreach (TypedConstant attributeArgument in attributeData.ConstructorArguments[0].Values)
|
||||
// Add dependent property notifications, if needed
|
||||
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, alsoNotifyForAttributeSymbol))
|
||||
{
|
||||
if (attributeArgument.Value is string dependentPropertyName)
|
||||
foreach (TypedConstant attributeArgument in attributeData.ConstructorArguments[0].Values)
|
||||
{
|
||||
// OnPropertyChanged("OtherPropertyName");
|
||||
setter = setter.AddStatements(ExpressionStatement(
|
||||
InvocationExpression(IdentifierName("OnPropertyChanged"))
|
||||
.AddArgumentListArguments(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(dependentPropertyName))))));
|
||||
if (attributeArgument.Value is string dependentPropertyName)
|
||||
{
|
||||
// OnPropertyChanged("OtherPropertyName");
|
||||
setter = setter.AddStatements(ExpressionStatement(
|
||||
InvocationExpression(IdentifierName("OnPropertyChanged"))
|
||||
.AddArgumentListArguments(Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(dependentPropertyName))))));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (validationAttributeSymbol is not null &&
|
||||
attributeData.AttributeClass?.InheritsFrom(validationAttributeSymbol) == true)
|
||||
{
|
||||
validationAttributes.Add(attributeData.AsAttributeSyntax());
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the generated property as follows:
|
||||
@@ -189,6 +199,7 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
|
||||
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
|
||||
// [global::System.Diagnostics.DebuggerNonUserCode]
|
||||
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
// <VALIDATION_ATTRIBUTES> // Optional
|
||||
// public <FIELD_TYPE> <PROPERTY_NAME>
|
||||
// {
|
||||
// get => <FIELD_NAME>;
|
||||
@@ -212,6 +223,7 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
|
||||
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(GetType().Assembly.GetName().Version.ToString())))))),
|
||||
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))),
|
||||
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))))
|
||||
.AddAttributeLists(validationAttributes.Select(static a => AttributeList(SingletonSeparatedList(a))).ToArray())
|
||||
.WithLeadingTrivia(leadingTrivia)
|
||||
.AddModifiers(Token(SyntaxKind.PublicKeyword))
|
||||
.AddAccessorListAccessors(
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
// 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 Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions
|
||||
{
|
||||
@@ -36,5 +41,78 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="AttributeSyntax"/> node that is equivalent to the input <see cref="AttributeData"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="attributeData">The input <see cref="AttributeData"/> instance to process.</param>
|
||||
/// <returns>An <see cref="AttributeSyntax"/> replicating the data in <paramref name="attributeData"/>.</returns>
|
||||
[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<ExpressionSyntax>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user