3
3
4
4
using System ;
5
5
using System . Collections . Generic ;
6
+ using System . Collections . Immutable ;
6
7
using System . Linq ;
7
8
using System . Threading ;
8
9
using Microsoft . CodeAnalysis ;
9
10
using Microsoft . CodeAnalysis . CSharp ;
11
+ using Microsoft . CodeAnalysis . DotnetRuntime . Extensions ;
10
12
11
13
namespace Microsoft . Extensions . Options . Generators
12
14
{
@@ -25,6 +27,7 @@ internal sealed class Emitter : EmitterBase
25
27
private string _staticValidationAttributeHolderClassFQN ;
26
28
private string _staticValidatorHolderClassFQN ;
27
29
private string _modifier ;
30
+ private string _TryGetValueNullableAnnotation ;
28
31
29
32
private sealed record StaticFieldInfo ( string FieldTypeFQN , int FieldOrder , string FieldName , IList < string > InstantiationLines ) ;
30
33
@@ -37,13 +40,14 @@ public Emitter(Compilation compilation, bool emitPreamble = true) : base(emitPre
37
40
else
38
41
{
39
42
_modifier = "internal" ;
40
- string suffix = $ "_{ new Random ( ) . Next ( ) : X8} ";
43
+ string suffix = $ "_{ GetNonRandomizedHashCode ( compilation . SourceModule . Name ) : X8} ";
41
44
_staticValidationAttributeHolderClassName += suffix ;
42
45
_staticValidatorHolderClassName += suffix ;
43
46
}
44
47
45
48
_staticValidationAttributeHolderClassFQN = $ "global::{ StaticFieldHolderClassesNamespace } .{ _staticValidationAttributeHolderClassName } ";
46
49
_staticValidatorHolderClassFQN = $ "global::{ StaticFieldHolderClassesNamespace } .{ _staticValidatorHolderClassName } ";
50
+ _TryGetValueNullableAnnotation = GetNullableAnnotationStringForTryValidateValueToUseInGeneratedCode ( compilation ) ;
47
51
}
48
52
49
53
public string Emit (
@@ -65,6 +69,31 @@ public string Emit(
65
69
return Capture ( ) ;
66
70
}
67
71
72
+ /// <summary>
73
+ /// Returns the nullable annotation string to use in the code generation according to the first parameter of
74
+ /// <see cref="System.ComponentModel.DataAnnotations.Validator.TryValidateValue(object, ValidationContext, ICollection{ValidationResult}, IEnumerable{ValidationAttribute})"/> is nullable annotated.
75
+ /// </summary>
76
+ /// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
77
+ /// <returns>"!" if the first parameter is not nullable annotated, otherwise an empty string.</returns>
78
+ /// <remarks>
79
+ /// In .NET 8.0 we have changed the nullable annotation on first parameter of the method cref="System.ComponentModel.DataAnnotations.Validator.TryValidateValue(object, ValidationContext, ICollection{ValidationResult}, IEnumerable{ValidationAttribute})"/>
80
+ /// The source generator need to detect if we need to append "!" to the first parameter of the method call when running on down-level versions.
81
+ /// </remarks>
82
+ private static string GetNullableAnnotationStringForTryValidateValueToUseInGeneratedCode ( Compilation compilation )
83
+ {
84
+ INamedTypeSymbol ? validatorTypeSymbol = compilation . GetBestTypeByMetadataName ( "System.ComponentModel.DataAnnotations.Validator" ) ;
85
+ if ( validatorTypeSymbol is not null )
86
+ {
87
+ ImmutableArray < ISymbol > members = validatorTypeSymbol . GetMembers ( "TryValidateValue" ) ;
88
+ if ( members . Length == 1 && members [ 0 ] is IMethodSymbol tryValidateValueMethod )
89
+ {
90
+ return tryValidateValueMethod . Parameters [ 0 ] . NullableAnnotation == NullableAnnotation . NotAnnotated ? "!" : string . Empty ;
91
+ }
92
+ }
93
+
94
+ return "!" ;
95
+ }
96
+
68
97
private void GenValidatorType ( ValidatorType vt , ref Dictionary < string , StaticFieldInfo > staticValidationAttributesDict , ref Dictionary < string , StaticFieldInfo > staticValidatorsDict )
69
98
{
70
99
if ( vt . Namespace . Length > 0 )
@@ -161,7 +190,7 @@ private void GenModelSelfValidationIfNecessary(ValidatedModel modelToValidate)
161
190
{
162
191
if ( modelToValidate . SelfValidates )
163
192
{
164
- OutLn ( $ "builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));") ;
193
+ OutLn ( $ "( builder ??= new()) .AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));") ;
165
194
OutLn ( ) ;
166
195
}
167
196
}
@@ -182,8 +211,7 @@ private void GenModelValidationMethod(
182
211
183
212
OutLn ( $ "public { ( makeStatic ? "static " : string . Empty ) } global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, { modelToValidate . Name } options)") ;
184
213
OutOpenBrace ( ) ;
185
- OutLn ( $ "var baseName = (string.IsNullOrEmpty(name) ? \" { modelToValidate . SimpleName } \" : name) + \" .\" ;") ;
186
- OutLn ( $ "var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();") ;
214
+ OutLn ( $ "global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null;") ;
187
215
OutLn ( $ "var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);") ;
188
216
189
217
int capacity = modelToValidate . MembersToValidate . Max ( static vm => vm . ValidationAttributes . Count ) ;
@@ -199,33 +227,33 @@ private void GenModelValidationMethod(
199
227
{
200
228
if ( vm . ValidationAttributes . Count > 0 )
201
229
{
202
- GenMemberValidation ( vm , ref staticValidationAttributesDict , cleanListsBeforeUse ) ;
230
+ GenMemberValidation ( vm , modelToValidate . SimpleName , ref staticValidationAttributesDict , cleanListsBeforeUse ) ;
203
231
cleanListsBeforeUse = true ;
204
232
OutLn ( ) ;
205
233
}
206
234
207
235
if ( vm . TransValidatorType is not null )
208
236
{
209
- GenTransitiveValidation ( vm , ref staticValidatorsDict ) ;
237
+ GenTransitiveValidation ( vm , modelToValidate . SimpleName , ref staticValidatorsDict ) ;
210
238
OutLn ( ) ;
211
239
}
212
240
213
241
if ( vm . EnumerationValidatorType is not null )
214
242
{
215
- GenEnumerationValidation ( vm , ref staticValidatorsDict ) ;
243
+ GenEnumerationValidation ( vm , modelToValidate . SimpleName , ref staticValidatorsDict ) ;
216
244
OutLn ( ) ;
217
245
}
218
246
}
219
247
220
248
GenModelSelfValidationIfNecessary ( modelToValidate ) ;
221
- OutLn ( $ "return builder.Build();") ;
249
+ OutLn ( $ "return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder .Build();") ;
222
250
OutCloseBrace ( ) ;
223
251
}
224
252
225
- private void GenMemberValidation ( ValidatedMember vm , ref Dictionary < string , StaticFieldInfo > staticValidationAttributesDict , bool cleanListsBeforeUse )
253
+ private void GenMemberValidation ( ValidatedMember vm , string modelName , ref Dictionary < string , StaticFieldInfo > staticValidationAttributesDict , bool cleanListsBeforeUse )
226
254
{
227
255
OutLn ( $ "context.MemberName = \" { vm . Name } \" ;") ;
228
- OutLn ( $ "context.DisplayName = baseName + \" { vm . Name } \" ;") ;
256
+ OutLn ( $ "context.DisplayName = string.IsNullOrEmpty(name) ? \" { modelName } . { vm . Name } \" : $ \" {{name}}. { vm . Name } \" ;") ;
229
257
230
258
if ( cleanListsBeforeUse )
231
259
{
@@ -239,9 +267,9 @@ private void GenMemberValidation(ValidatedMember vm, ref Dictionary<string, Stat
239
267
OutLn ( $ "validationAttributes.Add({ _staticValidationAttributeHolderClassFQN } .{ staticValidationAttributeInstance . FieldName } );") ;
240
268
}
241
269
242
- OutLn ( $ "if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.{ vm . Name } ! , context, validationResults, validationAttributes))") ;
270
+ OutLn ( $ "if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.{ vm . Name } { _TryGetValueNullableAnnotation } , context, validationResults, validationAttributes))") ;
243
271
OutOpenBrace ( ) ;
244
- OutLn ( $ "builder.AddResults(validationResults);") ;
272
+ OutLn ( $ "( builder ??= new()) .AddResults(validationResults);") ;
245
273
OutCloseBrace ( ) ;
246
274
}
247
275
@@ -305,7 +333,7 @@ private StaticFieldInfo GetOrAddStaticValidationAttribute(ref Dictionary<string,
305
333
return staticValidationAttributeInstance ;
306
334
}
307
335
308
- private void GenTransitiveValidation ( ValidatedMember vm , ref Dictionary < string , StaticFieldInfo > staticValidatorsDict )
336
+ private void GenTransitiveValidation ( ValidatedMember vm , string modelName , ref Dictionary < string , StaticFieldInfo > staticValidatorsDict )
309
337
{
310
338
string callSequence ;
311
339
if ( vm . TransValidateTypeIsSynthetic )
@@ -321,20 +349,22 @@ private void GenTransitiveValidation(ValidatedMember vm, ref Dictionary<string,
321
349
322
350
var valueAccess = ( vm . IsNullable && vm . IsValueType ) ? ".Value" : string . Empty ;
323
351
352
+ var baseName = $ "string.IsNullOrEmpty(name) ? \" { modelName } .{ vm . Name } \" : $\" {{name}}.{ vm . Name } \" ";
353
+
324
354
if ( vm . IsNullable )
325
355
{
326
356
OutLn ( $ "if (options.{ vm . Name } is not null)") ;
327
357
OutOpenBrace ( ) ;
328
- OutLn ( $ "builder.AddResult({ callSequence } .Validate(baseName + \" { vm . Name } \" , options.{ vm . Name } { valueAccess } ));") ;
358
+ OutLn ( $ "( builder ??= new()) .AddResult({ callSequence } .Validate({ baseName } , options.{ vm . Name } { valueAccess } ));") ;
329
359
OutCloseBrace ( ) ;
330
360
}
331
361
else
332
362
{
333
- OutLn ( $ "builder.AddResult({ callSequence } .Validate(baseName + \" { vm . Name } \" , options.{ vm . Name } { valueAccess } ));") ;
363
+ OutLn ( $ "( builder ??= new()) .AddResult({ callSequence } .Validate({ baseName } , options.{ vm . Name } { valueAccess } ));") ;
334
364
}
335
365
}
336
366
337
- private void GenEnumerationValidation ( ValidatedMember vm , ref Dictionary < string , StaticFieldInfo > staticValidatorsDict )
367
+ private void GenEnumerationValidation ( ValidatedMember vm , string modelName , ref Dictionary < string , StaticFieldInfo > staticValidatorsDict )
338
368
{
339
369
var valueAccess = ( vm . IsValueType && vm . IsNullable ) ? ".Value" : string . Empty ;
340
370
var enumeratedValueAccess = ( vm . EnumeratedIsNullable && vm . EnumeratedIsValueType ) ? ".Value" : string . Empty ;
@@ -365,22 +395,25 @@ private void GenEnumerationValidation(ValidatedMember vm, ref Dictionary<string,
365
395
{
366
396
OutLn ( $ "if (o is not null)") ;
367
397
OutOpenBrace ( ) ;
368
- OutLn ( $ "builder.AddResult({ callSequence } .Validate(baseName + $\" { vm . Name } [{{count}}]\" , o{ enumeratedValueAccess } ));") ;
398
+ var propertyName = $ "string.IsNullOrEmpty(name) ? $\" { modelName } .{ vm . Name } [{{count}}]\" : $\" {{name}}.{ vm . Name } [{{count}}]\" ";
399
+ OutLn ( $ "(builder ??= new()).AddResult({ callSequence } .Validate({ propertyName } , o{ enumeratedValueAccess } ));") ;
369
400
OutCloseBrace ( ) ;
370
401
371
402
if ( ! vm . EnumeratedMayBeNull )
372
403
{
373
404
OutLn ( $ "else") ;
374
405
OutOpenBrace ( ) ;
375
- OutLn ( $ "builder.AddError(baseName + $\" { vm . Name } [{{count}}] is null\" );") ;
406
+ var error = $ "string.IsNullOrEmpty(name) ? $\" { modelName } .{ vm . Name } [{{count}}] is null\" : $\" {{name}}.{ vm . Name } [{{count}}] is null\" ";
407
+ OutLn ( $ "(builder ??= new()).AddError({ error } );") ;
376
408
OutCloseBrace ( ) ;
377
409
}
378
410
379
411
OutLn ( $ "count++;") ;
380
412
}
381
413
else
382
414
{
383
- OutLn ( $ "builder.AddResult({ callSequence } .Validate(baseName + $\" { vm . Name } [{{count++}}]\" , o{ enumeratedValueAccess } ));") ;
415
+ var propertyName = $ "string.IsNullOrEmpty(name) ? $\" { modelName } .{ vm . Name } [{{count++}}] is null\" : $\" {{name}}.{ vm . Name } [{{count++}}] is null\" ";
416
+ OutLn ( $ "(builder ??= new()).AddResult({ callSequence } .Validate({ propertyName } , o{ enumeratedValueAccess } ));") ;
384
417
}
385
418
386
419
OutCloseBrace ( ) ;
@@ -405,5 +438,19 @@ private StaticFieldInfo GetOrAddStaticValidator(ref Dictionary<string, StaticFie
405
438
406
439
return staticValidatorInstance ;
407
440
}
441
+
442
+ /// <summary>
443
+ /// Returns a non-randomized hash code for the given string.
444
+ /// We always return a positive value.
445
+ /// </summary>
446
+ internal static int GetNonRandomizedHashCode ( string s )
447
+ {
448
+ uint result = 2166136261u ;
449
+ foreach ( char c in s )
450
+ {
451
+ result = ( c ^ result ) * 16777619 ;
452
+ }
453
+ return Math . Abs ( ( int ) result ) ;
454
+ }
408
455
}
409
456
}
0 commit comments