Skip to content

Commit e118a23

Browse files
committed
Merge branch 'releases/9.7.x'
2 parents 2ab4519 + bc4c7e2 commit e118a23

208 files changed

Lines changed: 2885 additions & 2343 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
### New Rules
22

3-
Rule ID | Category | Severity | Notes
4-
--------|----------|----------|-------
5-
TTRESG061 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
6-
TTRESG062 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
7-
TTRESG063 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
8-
TTRESG064 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
9-
TTRESG065 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
10-
TTRESG105 | ThinktectureRuntimeExtensionsAnalyzer | Warning | DiagnosticsDescriptors
3+
Rule ID | Category | Severity | Notes
4+
-----------|---------------------------------------|----------|------------------------
5+
TTRESG061 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
6+
TTRESG062 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
7+
TTRESG063 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
8+
TTRESG064 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
9+
TTRESG065 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
10+
TTRESG066 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
11+
TTRESG067 | ThinktectureRuntimeExtensionsAnalyzer | Error | DiagnosticsDescriptors
12+
TTRESG105 | ThinktectureRuntimeExtensionsAnalyzer | Warning | DiagnosticsDescriptors

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/CodeFixes/ThinktectureRuntimeExtensionsCodeFixProvider.cs

Lines changed: 145 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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;

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Constants.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ public static class ValueObject
1515
public const AccessModifier DEFAULT_CONSTRUCTOR_ACCESS_MODIFIER = AccessModifier.Private;
1616
}
1717

18+
public static class ValidationError
19+
{
20+
public const string NAME = "ValidationError";
21+
public const string NAMESPACE = "Thinktecture";
22+
public const string FULL_NAME = $"{NAMESPACE}.{NAME}";
23+
}
24+
1825
public static class Modules
1926
{
2027
public const string THINKTECTURE_RUNTIME_EXTENSIONS_JSON = "Thinktecture.Runtime.Extensions.Json.dll";
@@ -50,6 +57,18 @@ public static class IDisallowDefaultValue
5057
public const string NAME = "IDisallowDefaultValue";
5158
public const string NAMESPACE = "Thinktecture";
5259
}
60+
61+
public static class ObjectFactory
62+
{
63+
public const string NAME = "IObjectFactory";
64+
public const string NAMESPACE = "Thinktecture";
65+
}
66+
67+
public static class Convertible
68+
{
69+
public const string NAME = "IConvertible";
70+
public const string NAMESPACE = "Thinktecture";
71+
}
5372
}
5473

5574
public static class Attributes

0 commit comments

Comments
 (0)