@@ -88,8 +88,10 @@ internal static class Execute
88
88
ImmutableArray < string > . Builder propertyChangedNames = ImmutableArray . CreateBuilder < string > ( ) ;
89
89
ImmutableArray < string > . Builder propertyChangingNames = ImmutableArray . CreateBuilder < string > ( ) ;
90
90
ImmutableArray < string > . Builder notifiedCommandNames = ImmutableArray . CreateBuilder < string > ( ) ;
91
- ImmutableArray < AttributeInfo > . Builder validationAttributes = ImmutableArray . CreateBuilder < AttributeInfo > ( ) ;
91
+ ImmutableArray < AttributeInfo > . Builder forwardedAttributes = ImmutableArray . CreateBuilder < AttributeInfo > ( ) ;
92
92
bool alsoBroadcastChange = false ;
93
+ bool alsoValidateProperty = false ;
94
+ bool hasAnyValidationAttributes = false ;
93
95
94
96
// Track the property changing event for the property, if the type supports it
95
97
if ( shouldInvokeOnPropertyChanging )
@@ -118,28 +120,58 @@ internal static class Execute
118
120
continue ;
119
121
}
120
122
121
- // Track the current validation attribute, if applicable
123
+ // Check whether the property should also be validated
124
+ if ( TryGetIsValidatingProperty ( fieldSymbol , attributeData , builder , out bool isValidationTargetValid ) )
125
+ {
126
+ alsoValidateProperty = isValidationTargetValid ;
127
+
128
+ continue ;
129
+ }
130
+
131
+ // Track the current attribute for forwarding if it is a validation attribute
122
132
if ( attributeData . AttributeClass ? . InheritsFromFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.ValidationAttribute" ) == true )
123
133
{
124
- validationAttributes . Add ( AttributeInfo . From ( attributeData ) ) ;
134
+ hasAnyValidationAttributes = true ;
135
+
136
+ forwardedAttributes . Add ( AttributeInfo . From ( attributeData ) ) ;
137
+ }
138
+
139
+ // Also track the current attribute for forwarding if it is of any of the following types:
140
+ // - Display attributes (System.ComponentModel.DataAnnotations.DisplayAttribute)
141
+ // - UI hint attributes(System.ComponentModel.DataAnnotations.UIHintAttribute)
142
+ // - Scaffold column attributes (System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute)
143
+ // - Editable attributes (System.ComponentModel.DataAnnotations.EditableAttribute)
144
+ // - Key attributes (System.ComponentModel.DataAnnotations.KeyAttribute)
145
+ if ( attributeData . AttributeClass ? . HasOrInheritsFromFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.UIHintAttribute" ) == true ||
146
+ attributeData . AttributeClass ? . HasOrInheritsFromFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.ScaffoldColumnAttribute" ) == true ||
147
+ attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.DisplayAttribute" ) == true ||
148
+ attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.EditableAttribute" ) == true ||
149
+ attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::System.ComponentModel.DataAnnotations.KeyAttribute" ) == true )
150
+ {
151
+ forwardedAttributes . Add ( AttributeInfo . From ( attributeData ) ) ;
125
152
}
126
153
}
127
154
128
- // Log the diagnostics if needed
129
- if ( validationAttributes . Count > 0 &&
155
+ // Log the diagnostic for missing ObservableValidator, if needed
156
+ if ( hasAnyValidationAttributes &&
130
157
! fieldSymbol . ContainingType . InheritsFromFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator" ) )
131
158
{
132
159
builder . Add (
133
- MissingObservableValidatorInheritanceError ,
160
+ MissingObservableValidatorInheritanceForValidationAttributeError ,
134
161
fieldSymbol ,
135
162
fieldSymbol . ContainingType ,
136
163
fieldSymbol . Name ,
137
- validationAttributes . Count ) ;
164
+ forwardedAttributes . Count ) ;
165
+ }
138
166
139
- // Remove all validation attributes so that the generated code doesn't cause a build error about the
140
- // "ValidateProperty" method not existing (as the type doesn't inherit from ObservableValidator). The
141
- // compilation will still fail due to this diagnostics, but with just this easier to understand error.
142
- validationAttributes . Clear ( ) ;
167
+ // Log the diagnostic for missing validation attributes, if any
168
+ if ( alsoValidateProperty && ! hasAnyValidationAttributes )
169
+ {
170
+ builder . Add (
171
+ MissingValidationAttributesForAlsoValidatePropertyError ,
172
+ fieldSymbol ,
173
+ fieldSymbol . ContainingType ,
174
+ fieldSymbol . Name ) ;
143
175
}
144
176
145
177
diagnostics = builder . ToImmutable ( ) ;
@@ -152,7 +184,8 @@ internal static class Execute
152
184
propertyChangedNames . ToImmutable ( ) ,
153
185
notifiedCommandNames . ToImmutable ( ) ,
154
186
alsoBroadcastChange ,
155
- validationAttributes . ToImmutable ( ) ) ;
187
+ alsoValidateProperty ,
188
+ forwardedAttributes . ToImmutable ( ) ) ;
156
189
}
157
190
158
191
/// <summary>
@@ -412,6 +445,47 @@ private static bool TryGetIsBroadcastingChanges(
412
445
return false ;
413
446
}
414
447
448
+ /// <summary>
449
+ /// Checks whether a given generated property should also validate its value.
450
+ /// </summary>
451
+ /// <param name="fieldSymbol">The input <see cref="IFieldSymbol"/> instance to process.</param>
452
+ /// <param name="attributeData">The <see cref="AttributeData"/> instance for <paramref name="fieldSymbol"/>.</param>
453
+ /// <param name="diagnostics">The current collection of gathered diagnostics.</param>
454
+ /// <param name="isValidationTargetValid">Whether or not the the property is in a valid target that can validate values.</param>
455
+ /// <returns>Whether or not the generated property for <paramref name="fieldSymbol"/> used <c>[AlsoValidateProperty]</c>.</returns>
456
+ private static bool TryGetIsValidatingProperty (
457
+ IFieldSymbol fieldSymbol ,
458
+ AttributeData attributeData ,
459
+ ImmutableArray < Diagnostic > . Builder diagnostics ,
460
+ out bool isValidationTargetValid )
461
+ {
462
+ if ( attributeData . AttributeClass ? . HasFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.AlsoValidatePropertyAttribute" ) == true )
463
+ {
464
+ // If the containing type is valid, track it
465
+ if ( fieldSymbol . ContainingType . InheritsFromFullyQualifiedName ( "global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator" ) )
466
+ {
467
+ isValidationTargetValid = true ;
468
+
469
+ return true ;
470
+ }
471
+
472
+ // Otherwise just emit the diagnostic and then ignore the attribute
473
+ diagnostics . Add (
474
+ MissingObservableValidatorInheritanceForAlsoValidatePropertyError ,
475
+ fieldSymbol ,
476
+ fieldSymbol . ContainingType ,
477
+ fieldSymbol . Name ) ;
478
+
479
+ isValidationTargetValid = false ;
480
+
481
+ return true ;
482
+ }
483
+
484
+ isValidationTargetValid = false ;
485
+
486
+ return false ;
487
+ }
488
+
415
489
/// <summary>
416
490
/// Gets a <see cref="CompilationUnitSyntax"/> instance with the cached args for property changing notifications.
417
491
/// </summary>
@@ -505,10 +579,10 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
505
579
fieldExpression ,
506
580
IdentifierName ( "value" ) ) ) ) ;
507
581
508
- // If there are validation attributes , add a call to ValidateProperty:
582
+ // If validation is requested , add a call to ValidateProperty:
509
583
//
510
584
// ValidateProperty(value, <PROPERTY_NAME>);
511
- if ( propertyInfo . ValidationAttributes . Length > 0 )
585
+ if ( propertyInfo . AlsoValidateProperty )
512
586
{
513
587
setterStatements . Add (
514
588
ExpressionStatement (
@@ -594,9 +668,9 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
594
668
Argument ( IdentifierName ( "value" ) ) ) ) ,
595
669
Block ( setterStatements ) ) ;
596
670
597
- // Prepare the validation attributes, if any
598
- ImmutableArray < AttributeListSyntax > validationAttributes =
599
- propertyInfo . ValidationAttributes
671
+ // Prepare the forwarded attributes, if any
672
+ ImmutableArray < AttributeListSyntax > forwardedAttributes =
673
+ propertyInfo . ForwardedAttributes
600
674
. Select ( static a => AttributeList ( SingletonSeparatedList ( a . GetSyntax ( ) ) ) )
601
675
. ToImmutableArray ( ) ;
602
676
@@ -605,7 +679,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
605
679
// /// <inheritdoc cref="<FIELD_NAME>"/>
606
680
// [global::System.CodeDom.Compiler.GeneratedCode("...", "...")]
607
681
// [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
608
- // <VALIDATION_ATTRIBUTES >
682
+ // <FORWARDED_ATTRIBUTES >
609
683
// public <FIELD_TYPE><NULLABLE_ANNOTATION?> <PROPERTY_NAME>
610
684
// {
611
685
// get => <FIELD_NAME>;
@@ -624,7 +698,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
624
698
AttributeArgument ( LiteralExpression ( SyntaxKind . StringLiteralExpression , Literal ( typeof ( ObservablePropertyGenerator ) . Assembly . GetName ( ) . Version . ToString ( ) ) ) ) ) ) )
625
699
. WithOpenBracketToken ( Token ( TriviaList ( Comment ( $ "/// <inheritdoc cref=\" { propertyInfo . FieldName } \" />") ) , SyntaxKind . OpenBracketToken , TriviaList ( ) ) ) ,
626
700
AttributeList ( SingletonSeparatedList ( Attribute ( IdentifierName ( "global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage" ) ) ) ) )
627
- . AddAttributeLists ( validationAttributes . ToArray ( ) )
701
+ . AddAttributeLists ( forwardedAttributes . ToArray ( ) )
628
702
. AddModifiers ( Token ( SyntaxKind . PublicKeyword ) )
629
703
. AddAccessorListAccessors (
630
704
AccessorDeclaration ( SyntaxKind . GetAccessorDeclaration )
0 commit comments