@@ -21,6 +21,8 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
2121 private const string _DEFINE_VALUE_OBJECT_EQUALITY_COMPARER = "Define Value Object equality comparer" ;
2222 private const string _DEFINE_VALUE_OBJECT_COMPARER = "Define Value Object comparer" ;
2323 private const string _ALIGN_COMPARISON_EQUALITY_OPERATORS = "Align comparison/equality operators" ;
24+ private const string _GENERATE_VALIDATE_METHOD = "Generate Validate method" ;
25+ private const string _GENERATE_TO_VALUE_METHOD = "Generate ToValue method" ;
2426
2527 /// <inheritdoc />
2628 public override ImmutableArray < string > FixableDiagnosticIds { get ; } =
@@ -41,6 +43,8 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
4143 DiagnosticsDescriptors . UnionRecordMustBeSealed . Id ,
4244 DiagnosticsDescriptors . MembersDisallowingDefaultValuesMustBeRequired . Id ,
4345 DiagnosticsDescriptors . ComparisonAndEqualityOperatorsMismatch . Id ,
46+ DiagnosticsDescriptors . ObjectFactoryMustImplementStaticValidateMethod . Id ,
47+ DiagnosticsDescriptors . ObjectFactoryMustImplementToValueMethod . Id ,
4448 ] ;
4549
4650 /// <inheritdoc />
@@ -126,6 +130,14 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
126130 {
127131 context . RegisterCodeFix ( CodeAction . Create ( _MAKE_MEMBER_REQUIRED , _ => AddTypeModifierAsync ( context . Document , root , GetCodeFixesContext ( ) . MemberDeclaration , SyntaxKind . RequiredKeyword ) , _MAKE_MEMBER_REQUIRED ) , diagnostic ) ;
128132 }
133+ else if ( diagnostic . Id == DiagnosticsDescriptors . ObjectFactoryMustImplementStaticValidateMethod . Id )
134+ {
135+ context . RegisterCodeFix ( CodeAction . Create ( _GENERATE_VALIDATE_METHOD , t => GenerateValidateMethodAsync ( context . Document , root , GetCodeFixesContext ( ) . TypeDeclaration , t ) , _GENERATE_VALIDATE_METHOD ) , diagnostic ) ;
136+ }
137+ else if ( diagnostic . Id == DiagnosticsDescriptors . ObjectFactoryMustImplementToValueMethod . Id )
138+ {
139+ context . RegisterCodeFix ( CodeAction . Create ( _GENERATE_TO_VALUE_METHOD , t => GenerateToValueMethodAsync ( context . Document , root , GetCodeFixesContext ( ) . TypeDeclaration , t ) , _GENERATE_TO_VALUE_METHOD ) , diagnostic ) ;
140+ }
129141 }
130142 }
131143
@@ -400,9 +412,140 @@ private static async Task<Document> AddDefaultStringComparisonAsync(
400412 return newDoc ;
401413 }
402414
403- private static ThrowStatementSyntax BuildThrowNotImplementedException ( )
415+ private static async Task < Document > GenerateValidateMethodAsync (
416+ Document document ,
417+ SyntaxNode root ,
418+ TypeDeclarationSyntax ? declaration ,
419+ CancellationToken cancellationToken )
420+ {
421+ if ( declaration is null )
422+ return document ;
423+
424+ var model = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
425+
426+ if ( model is null )
427+ return document ;
428+
429+ var objectType = model . GetDeclaredSymbol ( declaration , cancellationToken ) ;
430+
431+ if ( objectType is null )
432+ return document ;
433+
434+ var validationErrorType = objectType . FindAttribute ( static attr => attr . IsValidationErrorAttribute ( ) ) ? . AttributeClass ? . TypeArguments [ 0 ] ;
435+ var newDeclaration = declaration ;
436+
437+ var attributes = objectType . GetAttributes ( ) ;
438+
439+ for ( var i = 0 ; i < attributes . Length ; i ++ )
440+ {
441+ var attribute = attributes [ i ] ;
442+
443+ if ( attribute . AttributeClass ? . IsObjectFactoryAttribute ( ) != true )
444+ continue ;
445+
446+ var valueType = attribute . AttributeClass . TypeArguments [ 0 ] ;
447+
448+ if ( objectType . HasValidateMethod ( valueType , validationErrorType ) )
449+ continue ;
450+
451+ var objectTypeName = SyntaxFactory . ParseTypeName ( objectType . ToMinimalDisplayString ( model , declaration . GetLocation ( ) . SourceSpan . Start ) ) ;
452+ var valueTypeName = SyntaxFactory . ParseTypeName ( valueType . WithNullableAnnotation ( NullableAnnotation . Annotated ) . ToMinimalDisplayString ( model , declaration . GetLocation ( ) . SourceSpan . Start ) ) ;
453+ var validationErrorTypeName = validationErrorType is null
454+ ? SyntaxFactory . ParseTypeName ( "ValidationError" )
455+ : SyntaxFactory . ParseTypeName ( validationErrorType . ToMinimalDisplayString ( model , declaration . GetLocation ( ) . SourceSpan . Start ) ) ;
456+
457+ var valueParameter = SyntaxFactory . Parameter ( SyntaxFactory . Identifier ( "value" ) )
458+ . WithType ( valueTypeName ) ;
459+
460+ var providerParameter = SyntaxFactory . Parameter ( SyntaxFactory . Identifier ( "provider" ) )
461+ . WithType ( SyntaxFactory . NullableType ( SyntaxFactory . ParseTypeName ( "IFormatProvider" ) ) ) ;
462+
463+ var itemOutType = objectType . IsReferenceType
464+ ? SyntaxFactory . NullableType ( objectTypeName )
465+ : objectTypeName ;
466+
467+ var itemParameter = SyntaxFactory . Parameter ( SyntaxFactory . Identifier ( "item" ) )
468+ . WithModifiers ( SyntaxFactory . TokenList ( SyntaxFactory . Token ( SyntaxKind . OutKeyword ) ) )
469+ . WithType ( itemOutType ) ;
470+
471+ var returnType = SyntaxFactory . NullableType ( validationErrorTypeName ) ;
472+
473+ var methodBody = SyntaxFactory . Block ( BuildThrowNotImplementedException ( false ) ) ;
474+
475+ var validateMethod = SyntaxFactory . MethodDeclaration ( returnType , "Validate" )
476+ . WithModifiers ( SyntaxFactory . TokenList (
477+ SyntaxFactory . Token ( SyntaxKind . PublicKeyword ) ,
478+ SyntaxFactory . Token ( SyntaxKind . StaticKeyword ) ) )
479+ . WithParameterList ( SyntaxFactory . ParameterList (
480+ SyntaxFactory . SeparatedList ( [ valueParameter , providerParameter , itemParameter ] ) ) )
481+ . WithBody ( methodBody )
482+ . WithLeadingTrivia ( SyntaxFactory . CarriageReturnLineFeed ) ;
483+
484+ newDeclaration = newDeclaration . AddMembers ( validateMethod ) ;
485+ }
486+
487+ var newRoot = root . ReplaceNode ( declaration , newDeclaration ) ;
488+ var newDoc = document . WithSyntaxRoot ( newRoot ) ;
489+
490+ return newDoc ;
491+ }
492+
493+ private static async Task < Document > GenerateToValueMethodAsync (
494+ Document document ,
495+ SyntaxNode root ,
496+ TypeDeclarationSyntax ? declaration ,
497+ CancellationToken cancellationToken )
498+ {
499+ if ( declaration is null )
500+ return document ;
501+
502+ var model = await document . GetSemanticModelAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
503+
504+ if ( model is null )
505+ return document ;
506+
507+ var objectType = model . GetDeclaredSymbol ( declaration , cancellationToken ) ;
508+
509+ if ( objectType is null )
510+ return document ;
511+
512+ var attributes = objectType . GetAttributes ( ) ;
513+ var newDeclaration = declaration ;
514+
515+ for ( var i = 0 ; i < attributes . Length ; i ++ )
516+ {
517+ var attribute = attributes [ i ] ;
518+
519+ if ( attribute . AttributeClass ? . IsObjectFactoryAttribute ( ) != true )
520+ continue ;
521+
522+ var valueType = attribute . AttributeClass . TypeArguments [ 0 ] ;
523+
524+ if ( ! attribute . NeedsToValueMethod ( ) || objectType . HasToValueMethod ( valueType ) )
525+ continue ;
526+
527+ var valueTypeName = SyntaxFactory . ParseTypeName ( valueType . ToMinimalDisplayString ( model , declaration . GetLocation ( ) . SourceSpan . Start ) ) ;
528+
529+ var methodBody = SyntaxFactory . Block ( BuildThrowNotImplementedException ( false ) ) ;
530+
531+ var toValueMethod = SyntaxFactory . MethodDeclaration ( valueTypeName , "ToValue" )
532+ . WithModifiers ( SyntaxFactory . TokenList ( SyntaxFactory . Token ( SyntaxKind . PublicKeyword ) ) )
533+ . WithParameterList ( SyntaxFactory . ParameterList ( ) )
534+ . WithBody ( methodBody )
535+ . WithLeadingTrivia ( SyntaxFactory . CarriageReturnLineFeed ) ;
536+
537+ newDeclaration = newDeclaration . AddMembers ( toValueMethod ) ;
538+ }
539+
540+ var newRoot = root . ReplaceNode ( declaration , newDeclaration ) ;
541+ var newDoc = document . WithSyntaxRoot ( newRoot ) ;
542+
543+ return newDoc ;
544+ }
545+
546+ private static ThrowStatementSyntax BuildThrowNotImplementedException ( bool fullyQualified = true )
404547 {
405- var notImplementedExceptionType = SyntaxFactory . ParseTypeName ( $ "global::System.{ nameof ( NotImplementedException ) } ") ;
548+ var notImplementedExceptionType = SyntaxFactory . ParseTypeName ( fullyQualified ? $ "global::System.{ nameof ( NotImplementedException ) } " : nameof ( NotImplementedException ) ) ;
406549 var newNotImplementedException = SyntaxFactory . ObjectCreationExpression ( notImplementedExceptionType , SyntaxFactory . ArgumentList ( ) , null ) ;
407550 var throwStatement = SyntaxFactory . ThrowStatement ( newNotImplementedException ) ;
408551 return throwStatement ;
0 commit comments