Skip to content

Commit

Permalink
Updated analyzers to handle generic replacements in redactions, and n…
Browse files Browse the repository at this point in the history
…ot enforce nullability
  • Loading branch information
mhelleborg committed Oct 16, 2024
1 parent 699a1c9 commit 16d0c45
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 14 deletions.
10 changes: 10 additions & 0 deletions Source/Analyzers/DescriptorRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ static class DescriptorRules
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "The field should be nullable since the value can be deleted.");

internal static readonly DiagnosticDescriptor IncorrectRedactableFieldType =
new(
DiagnosticIds.RedactableFieldIncorrectType,
title: "Generic Type must match the property type",
messageFormat: "The generic type for RedactablePersonalDataAttribute was {0}, must match the property type {1}",
DiagnosticCategories.Sdk,
DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: "The generic type must match the property type.");


internal static class Events
Expand Down
1 change: 1 addition & 0 deletions Source/Analyzers/DiagnosticIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public static class DiagnosticIds
public const string ExceptionInMutation = "SDK0010";

public const string NonNullableRedactableField = "SDK0011";
public const string RedactableFieldIncorrectType = "SDK0012";

/// <summary>
/// Aggregate missing the required Attribute.
Expand Down
43 changes: 31 additions & 12 deletions Source/Analyzers/RedactablePropertyAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ namespace Dolittle.SDK.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class RedactablePropertyAnalyzer : DiagnosticAnalyzer
{
static readonly DiagnosticDescriptor _rule = DescriptorRules.NonNullableRedactableField;
static readonly DiagnosticDescriptor _nonNullableRule = DescriptorRules.NonNullableRedactableField;
static readonly DiagnosticDescriptor _incorrectTypeRule = DescriptorRules.IncorrectRedactableFieldType;


/// <inheritdoc />
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [_rule];
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
[_nonNullableRule, _incorrectTypeRule];

/// <inheritdoc />
public override void Initialize(AnalysisContext context)
Expand All @@ -38,21 +41,37 @@ private static void AnalyzeProperty(SyntaxNodeAnalysisContext context)

if (propertySymbol == null) return;

var hasAttribute = propertySymbol.GetAttributes()
.Any(attr => attr.AttributeClass?.Name == "RedactablePersonalDataAttribute");
var personalDataAttribute = propertySymbol.GetAttributes()
.FirstOrDefault(attr => attr.AttributeClass?.Name == "RedactablePersonalDataAttribute");

if (personalDataAttribute == null) return;

if (!hasAttribute) return;
if (personalDataAttribute.AttributeClass is { IsGenericType: true } namedTypeSymbol)
{
// If the RedactablePersonalDataAttribute is generic, the type parameter must match the property type
var typeArgument = namedTypeSymbol.TypeArguments.First();
if (!typeArgument.Equals(propertySymbol.Type, SymbolEqualityComparer.Default))
{
context.ReportDiagnostic(Diagnostic.Create(_incorrectTypeRule, personalDataAttribute.ApplicationSyntaxReference?.GetSyntax().GetLocation() ?? propertyDeclaration.GetLocation(),
typeArgument.Name, propertySymbol.Type.Name));
}

if (propertySymbol.Type.NullableAnnotation != NullableAnnotation.Annotated)
}
else
{
context.ReportDiagnostic(Diagnostic.Create(_rule, propertyDeclaration.GetLocation(), propertySymbol.Name));
// If it the non
if (propertySymbol.Type.NullableAnnotation != NullableAnnotation.Annotated)
{
context.ReportDiagnostic(Diagnostic.Create(_nonNullableRule, propertyDeclaration.GetLocation(),
propertySymbol.Name));
}
}
}

private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context)
{
var recordDeclaration = (RecordDeclarationSyntax)context.Node;

// Check if it's a record with a primary constructor
if (recordDeclaration.ParameterList == null) return;

Expand All @@ -65,13 +84,13 @@ private static void AnalyzeRecordDeclaration(SyntaxNodeAnalysisContext context)
{
continue;
}

var parameterSymbol = context.SemanticModel.GetDeclaredSymbol(parameter);
if (!IsNullable(parameterSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(_rule, parameter.GetLocation(), parameterSymbol.Name));
context.ReportDiagnostic(Diagnostic.Create(_nonNullableRule, parameter.GetLocation(),
parameterSymbol.Name));
}

}
}

Expand Down
73 changes: 73 additions & 0 deletions Tests/Analyzers/Diagnostics/RedactablePropertyAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,60 @@ class SomeEvent
await VerifyAnalyzerFindsNothingAsync(code);
}

[Fact]
public async Task OnPropertyWithReplacedRedactableStringAttribute()
{
var code = @"
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Redaction;
[EventType(""44a755a7-e755-4076-bad4-fbc6ec3c0ea5"")]
class SomeEvent
{
[RedactablePersonalData<string>(""<removed>"")]
public string SomeProperty { get; set; }
}
";

await VerifyAnalyzerFindsNothingAsync(code);
}

[Fact]
public async Task OnPropertyWithReplacedRedactableStringAttributeWithDifferentNullability()
{
var code = @"
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Redaction;
[EventType(""44a755a7-e755-4076-bad4-fbc6ec3c0ea5"")]
class SomeEvent
{
[RedactablePersonalData<string>(""<removed>"")]
public string? SomeProperty { get; set; }
}
";

await VerifyAnalyzerFindsNothingAsync(code);
}

[Fact]
public async Task OnPropertyWithReplacedRedactableIntAttribute()
{
var code = @"
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Redaction;
[EventType(""44a755a7-e755-4076-bad4-fbc6ec3c0ea5"")]
class SomeEvent
{
[RedactablePersonalData<int>(-1)]
public int SomeProperty { get; set; }
}
";

await VerifyAnalyzerFindsNothingAsync(code);
}

[Fact]
public async Task OnPropertyWithNonNullableRedactableAttribute()
{
Expand All @@ -60,6 +114,25 @@ await VerifyAnalyzerAsync(code,
.WithSpan(8, 5, 9, 45).WithArguments("SomeProperty"));
}

[Fact]
public async Task OnPropertyWithIntAttributeOnString()
{
var code = @"
using Dolittle.SDK.Events;
using Dolittle.SDK.Events.Redaction;
[EventType(""44a755a7-e755-4076-bad4-fbc6ec3c0ea5"")]
class SomeEvent
{
[RedactablePersonalData<int>(123)]
public string SomeProperty { get; set; }
}
";
await VerifyAnalyzerAsync(code,
Diagnostic(DescriptorRules.IncorrectRedactableFieldType)
.WithSpan(8, 6, 8, 38).WithMessage("The generic type for RedactablePersonalDataAttribute was Int32, must match the property type String"));
}

[Fact]
public async Task OnRecordPropertyWithNonNullableRedactableAttribute()
{
Expand Down
4 changes: 2 additions & 2 deletions Tests/Events/Redaction/RedactionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public void WhenTypeHasRedactableProperties()
RedactedType<RedactedEvent>.RedactedProperties
.Should().BeEquivalentTo(new Dictionary<string, object?>
{
{ "SomeVal", -1 },
{ "SomeVal", null },
{ "SomeImportantPii", "<fjernet pga gdpr-forespørsel>" },
{ "BirthDate", null },
{ "BirthDate", -1 },
});
}

Expand Down

0 comments on commit 16d0c45

Please sign in to comment.