From c4d510a9337bc45b4f62baf07f6914aac3f9f340 Mon Sep 17 00:00:00 2001 From: Magne Helleborg Date: Thu, 7 Nov 2024 13:38:49 +0100 Subject: [PATCH] Maintenance release (#248) * Added codefix for CS7036, when no ID has been assigned to Dolittle attributes * Upgraded MongoDB dependencies --- Directory.Packages.props | 4 +- .../AttributeIdentityCodeFixProvider.cs | 63 ++++++++++- Tests/Analyzers/Analyzers.csproj | 1 + .../AnnotationIdentityCodeFixTests.cs | 106 ++++++++++++++++++ 4 files changed, 171 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 53919131..4d7fe5bf 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -39,8 +39,8 @@ - - + + diff --git a/Source/Analyzers/CodeFixes/AttributeIdentityCodeFixProvider.cs b/Source/Analyzers/CodeFixes/AttributeIdentityCodeFixProvider.cs index 924e3b60..3b60316d 100644 --- a/Source/Analyzers/CodeFixes/AttributeIdentityCodeFixProvider.cs +++ b/Source/Analyzers/CodeFixes/AttributeIdentityCodeFixProvider.cs @@ -1,8 +1,10 @@ // Copyright (c) Dolittle. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Immutable; using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -21,14 +23,25 @@ namespace Dolittle.SDK.Analyzers.CodeFixes; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AttributeIdentityCodeFixProvider)), Shared] public class AttributeIdentityCodeFixProvider : CodeFixProvider { + const string NoArgumentCorrespondsToRequiredParameter = "CS7036"; + /// - public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIds.AttributeInvalidIdentityRuleId, DiagnosticIds.RedactionEventIncorrectPrefix); + public override ImmutableArray FixableDiagnosticIds { get; } = [ + NoArgumentCorrespondsToRequiredParameter, + DiagnosticIds.AttributeInvalidIdentityRuleId, + DiagnosticIds.RedactionEventIncorrectPrefix]; /// public override Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var diagnostic = context.Diagnostics[0]; + if(diagnostic.Id.Equals(NoArgumentCorrespondsToRequiredParameter, StringComparison.Ordinal)) + { + RegisterCs7036IfApplicable(context, diagnostic, document); + return Task.CompletedTask; + } + if (!diagnostic.Properties.TryGetValue("identityParameter", out var identityParameterName)) { return Task.CompletedTask; @@ -54,6 +67,43 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) return Task.CompletedTask; } + void RegisterCs7036IfApplicable(CodeFixContext context, Diagnostic diagnostic, Document document) + { + try + { + var (identityParameterName, type) = Extract7036Arguments(diagnostic); + + switch (type, identityParameterName) + { + case ("EventTypeAttribute", "eventTypeId"): + case ("EventHandlerAttribute", "eventHandlerId"): + case ("ProjectionAttribute", "projectionId"): + case ("AggregateRootAttribute", "id"): + break; + default: + return; + } + + context.RegisterCodeFix( + CodeAction.Create( + "Add identity", + ct => AddIdentity(context, document, identityParameterName, IdentityGenerator.Generate, ct), + nameof(AttributeIdentityCodeFixProvider) + ".AddIdentity"), + diagnostic); + + } + catch + { + // ignored + } + } + + (string parameterName, string typeName) Extract7036Arguments(Diagnostic diagnostic) + { + var message = diagnostic.GetMessage(); + var parts = message.Split('\''); + return (parts[1], parts[3].Split('.').FirstOrDefault()); + } static async Task UpdateIdentity(CodeFixContext context, Document document, string identityParameterName, CreateIdentity createIdentity, @@ -65,6 +115,17 @@ static async Task UpdateIdentity(CodeFixContext context, Document docu var updatedRoot = root.ReplaceNode(attribute, GenerateIdentityAttribute(attribute, identityParameterName, createIdentity)); return document.WithSyntaxRoot(updatedRoot); } + + static async Task AddIdentity(CodeFixContext context, Document document, string identityParameterName, + CreateIdentity createIdentity, + CancellationToken cancellationToken) + { + var root = await context.Document.GetSyntaxRootAsync(cancellationToken); + if (root is null) return document; + if (!TryGetTargetNode(context, root, out AttributeSyntax attribute)) return document; // Target not found + var updatedRoot = root.ReplaceNode(attribute, GenerateIdentityAttribute(attribute, identityParameterName, createIdentity)); + return document.WithSyntaxRoot(updatedRoot); + } static AttributeSyntax GenerateIdentityAttribute(AttributeSyntax existing, string identityParameterName, CreateIdentity generate) { diff --git a/Tests/Analyzers/Analyzers.csproj b/Tests/Analyzers/Analyzers.csproj index c09759b5..9542daf0 100644 --- a/Tests/Analyzers/Analyzers.csproj +++ b/Tests/Analyzers/Analyzers.csproj @@ -8,6 +8,7 @@ + diff --git a/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs b/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs index ffaaf5e1..98fdca95 100644 --- a/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs +++ b/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs @@ -34,6 +34,112 @@ class SomeEvent await VerifyCodeFixAsync(test, expected, diagnosticResult); } + [Fact] + public async Task FixEventTypeAttributeWithNoIdentity() + { + var test = @" +using Dolittle.SDK.Events; + +[EventType] +class SomeEvent +{ + public string Name {get; set;} +}"; + + var expected = @" +using Dolittle.SDK.Events; + +[EventType(""61359cf4-3ae7-4a26-8a81-6816d3877f81"")] +class SomeEvent +{ + public string Name {get; set;} +}"; + IdentityGenerator.Override = "61359cf4-3ae7-4a26-8a81-6816d3877f81"; + var diagnosticResult = DiagnosticResult.CompilerError("CS7036") + .WithSpan(4, 2, 4, 11) + .WithArguments("eventTypeId", "Dolittle.SDK.Events.EventTypeAttribute.EventTypeAttribute(string, uint, string?)"); + await VerifyCodeFixAsync(test, expected, diagnosticResult); + } + + [Fact] + public async Task FixEventHandlerAttributeWithNoIdentity() + { + var test = @" +using Dolittle.SDK.Events.Handling; + +[EventHandler] +class SomeHandler +{ + public string Name {get; set;} +}"; + + var expected = @" +using Dolittle.SDK.Events.Handling; + +[EventHandler(""61359cf4-3ae7-4a26-8a81-6816d3877f81"")] +class SomeHandler +{ + public string Name {get; set;} +}"; + IdentityGenerator.Override = "61359cf4-3ae7-4a26-8a81-6816d3877f81"; + var diagnosticResult = DiagnosticResult.CompilerError("CS7036") + .WithSpan(4, 2, 4, 14) + .WithArguments("eventHandlerId", "Dolittle.SDK.Events.Handling.EventHandlerAttribute.EventHandlerAttribute(string, bool, string?, string?, int, Dolittle.SDK.Events.Handling.ProcessFrom, string?, string?)"); + await VerifyCodeFixAsync(test, expected, diagnosticResult); + } + + [Fact] + public async Task FixProjectionAttributeWithNoIdentity() + { + var test = @" +using Dolittle.SDK.Projections; + +[Projection] +class SomeProjection: ReadModel +{ + public string Name {get; set;} +}"; + + var expected = @" +using Dolittle.SDK.Projections; + +[Projection(""61359cf4-3ae7-4a26-8a81-6816d3877f81"")] +class SomeProjection: ReadModel +{ + public string Name {get; set;} +}"; + IdentityGenerator.Override = "61359cf4-3ae7-4a26-8a81-6816d3877f81"; + var diagnosticResult = DiagnosticResult.CompilerError("CS7036") + .WithSpan(4, 2, 4, 12) + .WithArguments("projectionId", "Dolittle.SDK.Projections.ProjectionAttribute.ProjectionAttribute(string, string?, string?, string?, bool)"); + await VerifyCodeFixAsync(test, expected, diagnosticResult); + } + + [Fact] + public async Task FixAggregateRootAttributeWithNoIdentity() + { + var test = @" +using Dolittle.SDK.Aggregates; + +[AggregateRoot] +class SomeAggregate: AggregateRoot +{ +}"; + + var expected = @" +using Dolittle.SDK.Aggregates; + +[AggregateRoot(""61359cf4-3ae7-4a26-8a81-6816d3877f81"")] +class SomeAggregate: AggregateRoot +{ +}"; + IdentityGenerator.Override = "61359cf4-3ae7-4a26-8a81-6816d3877f81"; + + var diagnosticResult = DiagnosticResult.CompilerError("CS7036") + .WithSpan(4, 2, 4, 15) + .WithArguments("id", "Dolittle.SDK.Aggregates.AggregateRootAttribute.AggregateRootAttribute(string, string?)"); + await VerifyCodeFixAsync(test, expected, diagnosticResult); + } [Fact] public async Task FixesAttributeWithInvalidIdentityWithNamedArguments() {