Skip to content

Commit

Permalink
Refactoring, codefix & tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mhelleborg committed Oct 21, 2024
1 parent 52ab322 commit 5ab44df
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 27 deletions.
41 changes: 22 additions & 19 deletions Source/Analyzers/AttributeIdentityAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ void CheckAttributeIdentity(AttributeSyntax attribute, IMethodSymbol symbol, Syn
if (!TryGetStringValue(attribute, identityParameter, context, out var identityText)) return;
var attributeName = attribute.Name.ToString();

if (FlagRedactionIdentity(symbol, attribute, context, identityText!)) return;
if (FlagRedactionIdentity(symbol, attribute, context, identityParameter, attributeName, identityText!)) return;

if (!Guid.TryParse(identityText!.Trim('"'), out var identifier))
{
Expand All @@ -165,7 +165,9 @@ void CheckAttributeIdentity(AttributeSyntax attribute, IMethodSymbol symbol, Syn
}
}

bool FlagRedactionIdentity(IMethodSymbol symbol, AttributeSyntax attribute, SyntaxNodeAnalysisContext context, string identifier)
bool FlagRedactionIdentity(IMethodSymbol symbol, AttributeSyntax attribute, SyntaxNodeAnalysisContext context,
IParameterSymbol identityParameter,
string attributeName, string identifier)
{
// Only relevant for EventTypeAttribute on a class extending PersonalDataRedacted
if (symbol.ReceiverType?.Name != "EventTypeAttribute") return false;
Expand All @@ -175,29 +177,30 @@ bool FlagRedactionIdentity(IMethodSymbol symbol, AttributeSyntax attribute, Synt
// At this point we know that the attribute is an EventTypeAttribute on a class extending PersonalDataRedacted
// If the identifier does not contain the redaction prefix, we report an error

if (Guid.TryParse(identifier.Trim('"'), out var guid))
if (IsValidRedactionIdentifier())
{
if (guid.ToString()
.StartsWith(DolittleConstants.Identifiers.RedactionIdentityPrefix,
StringComparison.InvariantCultureIgnoreCase))
{
return false;
}

context.ReportDiagnostic(Diagnostic.Create(DescriptorRules.IncorrectRedactedEventTypePrefix,
attribute.GetLocation(), identifier));
return true;
return false;
}

context.ReportDiagnostic(Diagnostic.Create(DescriptorRules.IncorrectRedactedEventTypePrefix,
attribute.GetLocation(),
properties: ImmutableDictionary<string, string?>.Empty.Add("identityParameter", identityParameter.Name),
attributeName, identityParameter.Name, identifier));

return true;


if (!identifier.StartsWith(DolittleConstants.Identifiers.RedactionIdentityPrefix,
StringComparison.InvariantCultureIgnoreCase))
bool IsValidRedactionIdentifier()
{
context.ReportDiagnostic(Diagnostic.Create(DescriptorRules.IncorrectRedactedEventTypePrefix,
attribute.GetLocation(), identifier));
}
if (!Guid.TryParse(identifier.Trim('"'), out var guid))
{
return false;
}

return true;
var asString = guid.ToString();
return asString.StartsWith(DolittleConstants.Identifiers.RedactionIdentityPrefix,
StringComparison.InvariantCultureIgnoreCase);
}
}

static bool TryGetStringValue(AttributeSyntax attribute, IParameterSymbol parameter,
Expand Down
22 changes: 16 additions & 6 deletions Source/Analyzers/CodeFixes/AttributeIdentityCodeFixProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@

namespace Dolittle.SDK.Analyzers.CodeFixes;

delegate string CreateIdentity();

/// <summary>
/// Generates a valid Guid identity for a given Dolittle identity attribute
/// </summary>
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AttributeIdentityCodeFixProvider)), Shared]
public class AttributeIdentityCodeFixProvider : CodeFixProvider
{
/// <inheritdoc />
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIds.AttributeInvalidIdentityRuleId);
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(DiagnosticIds.AttributeInvalidIdentityRuleId, DiagnosticIds.RedactionEventIncorrectPrefix);

/// <inheritdoc />
public override Task RegisterCodeFixesAsync(CodeFixContext context)
Expand All @@ -36,29 +38,37 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
case DiagnosticIds.AttributeInvalidIdentityRuleId:
context.RegisterCodeFix(
CodeAction.Create(
"Generate identity", ct => GenerateIdentity(context, document, identityParameterName!, ct),
"Generate identity", ct => UpdateIdentity(context, document, identityParameterName!,IdentityGenerator.Generate , ct),
nameof(AttributeIdentityCodeFixProvider) + ".AddIdentity"),
diagnostic);
break;
case DiagnosticIds.RedactionEventIncorrectPrefix:
context.RegisterCodeFix(
CodeAction.Create(
"Generate redaction identity", ct => UpdateIdentity(context, document, identityParameterName!,IdentityGenerator.GenerateRedactionId , ct),
nameof(AttributeIdentityCodeFixProvider) + ".AddRedactionIdentity"),
diagnostic);
break;
}

return Task.CompletedTask;
}


static async Task<Document> GenerateIdentity(CodeFixContext context, Document document, string identityParameterName,
static async Task<Document> UpdateIdentity(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, GenerateIdentity(attribute, identityParameterName));
var updatedRoot = root.ReplaceNode(attribute, GenerateIdentityAttribute(attribute, identityParameterName, createIdentity));
return document.WithSyntaxRoot(updatedRoot);
}

static AttributeSyntax GenerateIdentity(AttributeSyntax existing, string identityParameterName)
static AttributeSyntax GenerateIdentityAttribute(AttributeSyntax existing, string identityParameterName, CreateIdentity generate)
{
var newIdentity = SyntaxFactory.ParseExpression("\"" + IdentityGenerator.Generate() + "\"");
var newIdentity = SyntaxFactory.ParseExpression("\"" + generate.Invoke() + "\"");
if (existing.ArgumentList is not null && existing.TryGetArgumentValue(identityParameterName, 0, out var oldIdentity))
{
return existing.ReplaceNode(oldIdentity, newIdentity);
Expand Down
14 changes: 14 additions & 0 deletions Source/Analyzers/IdentityGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,21 @@ namespace Dolittle.SDK.Analyzers;
/// </summary>
static class IdentityGenerator
{
static string? _overrideRedaction;
internal static string? Override { get; set; }

internal static string? OverrideRedaction
{
get => _overrideRedaction;
set
{
if(value is not null && !value.StartsWith(DolittleConstants.Identifiers.RedactionIdentityPrefix))
throw new ArgumentException("Redaction identity must start with 'redaction-'");
_overrideRedaction = value;
}
}

public static string Generate() => Override ?? Guid.NewGuid().ToString();

public static string GenerateRedactionId() => OverrideRedaction ?? DolittleConstants.Identifiers.RedactionIdentityPrefix + Guid.NewGuid().ToString().Substring(DolittleConstants.Identifiers.RedactionIdentityPrefix.Length);
}
1 change: 1 addition & 0 deletions Tests/Analyzers/Analyzers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit"/>
Expand Down
34 changes: 34 additions & 0 deletions Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,39 @@ class SomeAggregate: AggregateRoot
.WithArguments("AggregateRoot", "id", "invalid-id");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

[Fact]
public async Task WhenFixingRedactionEvents()
{
var test = @"
[Dolittle.SDK.Events.EventType(""e8879da9-fd28-4c78-b9cc-1381a09c3e79"")]
class SomeEvent
{
public string Name {get; set;}
};
[Dolittle.SDK.Events.EventType(""hello-0000-da7a-aaaa-fbc6ec3c0ea6"")]
class RedactionEvent: Dolittle.SDK.Events.Redaction.PersonalDataRedactedForEvent<SomeEvent>
{
}";

var expected = @"
[Dolittle.SDK.Events.EventType(""e8879da9-fd28-4c78-b9cc-1381a09c3e79"")]
class SomeEvent
{
public string Name {get; set;}
};
[Dolittle.SDK.Events.EventType(""de1e7e17-bad5-da7a-8a81-6816d3877f81"")]
class RedactionEvent: Dolittle.SDK.Events.Redaction.PersonalDataRedactedForEvent<SomeEvent>
{
}";
IdentityGenerator.OverrideRedaction = "de1e7e17-bad5-da7a-8a81-6816d3877f81";

var diagnosticResult = Diagnostic(DescriptorRules.IncorrectRedactedEventTypePrefix)
.WithSpan(8, 2, 8, 68)
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "hello-0000-da7a-aaaa-fbc6ec3c0ea6");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}
}

4 changes: 2 additions & 2 deletions Tests/Analyzers/Diagnostics/RedactionEventTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class RedactionEvent: Dolittle.SDK.Events.Redaction.PersonalDataRedactedForEvent
{
Diagnostic(DescriptorRules.IncorrectRedactedEventTypePrefix)
.WithSpan(8, 2, 8, 71)
.WithArguments("de1e7e17-0000-da7a-aaaa-fbc6ec3c0ea6"),
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "de1e7e17-0000-da7a-aaaa-fbc6ec3c0ea6"),
};

await VerifyAnalyzerAsync(test, expected);
Expand All @@ -94,7 +94,7 @@ class RedactionEvent: Dolittle.SDK.Events.Redaction.PersonalDataRedactedForEvent
{
Diagnostic(DescriptorRules.IncorrectRedactedEventTypePrefix)
.WithSpan(8, 2, 8, 68)
.WithArguments("hello-0000-da7a-aaaa-fbc6ec3c0ea6"),
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "hello-0000-da7a-aaaa-fbc6ec3c0ea6"),
};

await VerifyAnalyzerAsync(test, expected);
Expand Down
29 changes: 29 additions & 0 deletions Tests/Analyzers/GenerateIdTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 FluentAssertions;

namespace Dolittle.SDK.Analyzers;

public class GenerateIdTests
{
[Fact]
public void WhenGeneratingId()
{
var id = IdentityGenerator.GenerateRedactionId();

id.Should().NotBeEmpty();
Guid.TryParse(id, out _).Should().BeTrue("Should be a valid Guid");
}

[Fact]
public void WhenGeneratingRedactionId()
{
var id = IdentityGenerator.GenerateRedactionId();

id.Should().NotBeEmpty();
Guid.TryParse(id, out _).Should().BeTrue("Should be a valid Guid");
id.Should().StartWith("de1e7e17-bad5-da7a-", "Should start with the correct prefix");
}
}

0 comments on commit 5ab44df

Please sign in to comment.