Skip to content

Commit

Permalink
Let analyzers support constants for attribute identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
mhelleborg committed Oct 18, 2024
1 parent b21653a commit eb7c367
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 14 deletions.
36 changes: 33 additions & 3 deletions Source/Analyzers/AttributeIdentityAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,10 @@ static bool TypeExtends(INamedTypeSymbol? typeSymbol, INamedTypeSymbol? baseClas
void CheckAttributeIdentity(AttributeSyntax attribute, IMethodSymbol symbol, SyntaxNodeAnalysisContext context)
{
var identityParameter = symbol.Parameters[0];
if (!attribute.TryGetArgumentValue(identityParameter, out var id)) return;
var identityText = id.GetText().ToString();
if (!TryGetStringValue(attribute, identityParameter, context, out var identityText)) return;
var attributeName = attribute.Name.ToString();

if (!Guid.TryParse(identityText.Trim('"'), out var identifier))
if (!Guid.TryParse(identityText!.Trim('"'), out var identifier))
{
var properties = ImmutableDictionary<string, string?>.Empty.Add("identityParameter", identityParameter.Name);
context.ReportDiagnostic(Diagnostic.Create(DescriptorRules.InvalidIdentity, attribute.GetLocation(), properties,
Expand All @@ -149,6 +148,37 @@ void CheckAttributeIdentity(AttributeSyntax attribute, IMethodSymbol symbol, Syn
}
}

static bool TryGetStringValue(AttributeSyntax attribute, IParameterSymbol parameter, SyntaxNodeAnalysisContext context, out string? argumentString)
{
if (!attribute.TryGetArgumentValue(parameter, out var expression))
{
argumentString = null;
return true;
}

// Check if the argument is a string literal or a constant
if (expression is LiteralExpressionSyntax { Token.Value: string value })
{
argumentString = value;
return true;
}

if (expression is MemberAccessExpressionSyntax memberAccess)
{
// If the argument is a member access, check if it's a constant
// Then retrieve the constant value
var symbol = context.SemanticModel.GetSymbolInfo(memberAccess).Symbol;
if (symbol is IFieldSymbol { HasConstantValue: true } field)
{
argumentString = field.ConstantValue?.ToString();
return true;
}
}

argumentString = null;
return false;
}

static void ReportDuplicateIdentity(AttributeSyntax attribute, SyntaxNodeAnalysisContext context, Guid identifier) =>
context.ReportDiagnostic(
Diagnostic.Create(DescriptorRules.DuplicateIdentity, attribute.GetLocation(), attribute.Name.ToString(), identifier.ToString()));
Expand Down
14 changes: 7 additions & 7 deletions Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SomeEvent
IdentityGenerator.Override = "61359cf4-3ae7-4a26-8a81-6816d3877f81";
var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 15)
.WithArguments("EventType", "eventTypeId", @"""""");
.WithArguments("EventType", "eventTypeId", "");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand Down Expand Up @@ -58,7 +58,7 @@ class SomeEvent

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 28)
.WithArguments("EventType", "eventTypeId", @"""""");
.WithArguments("EventType", "eventTypeId", "");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand Down Expand Up @@ -86,7 +86,7 @@ class SomeEvent

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 42)
.WithArguments("EventType", "eventTypeId", @"""""");
.WithArguments("EventType", "eventTypeId", "");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand All @@ -112,7 +112,7 @@ class SomeHandler

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 58)
.WithArguments("EventHandler", "eventHandlerId", @"""invalid-id""");
.WithArguments("EventHandler", "eventHandlerId", "invalid-id");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand All @@ -138,7 +138,7 @@ class SomeProjection: ReadModel

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 16)
.WithArguments("Projection", "projectionId", "\"\"");
.WithArguments("Projection", "projectionId", "");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand All @@ -164,7 +164,7 @@ class SomeProjection: ReadModel

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 54)
.WithArguments("Projection", "projectionId", @"""invalid-id""");
.WithArguments("Projection", "projectionId", "invalid-id");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}

Expand All @@ -190,7 +190,7 @@ class SomeAggregate: AggregateRoot

var diagnosticResult = Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 47)
.WithArguments("AggregateRoot", "id", @"""invalid-id""");
.WithArguments("AggregateRoot", "id", "invalid-id");
await VerifyCodeFixAsync(test, expected, diagnosticResult);
}
}
Expand Down
38 changes: 34 additions & 4 deletions Tests/Analyzers/Diagnostics/AnnotationAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class SomeEvent
{
Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(4, 2, 4, 15)
.WithArguments("EventType", "eventTypeId", "\"\"")
.WithArguments("EventType", "eventTypeId", "")
};

await VerifyAnalyzerAsync(test, expected);
Expand All @@ -71,7 +71,7 @@ class SomeEvent
{
Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(2, 2, 2, 35)
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "\"\""),
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", ""),
};

await VerifyAnalyzerAsync(test, expected);
Expand Down Expand Up @@ -102,6 +102,36 @@ class SomeEvent

await VerifyAnalyzerFindsNothingAsync(test);
}

[Fact]
public async Task WhenHasConstAsId()
{
var test = @"
[Dolittle.SDK.Events.EventType(Id)]
class SomeEvent
{
const string Id = ""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"";
public string Name {get; set;}
}";

await VerifyAnalyzerFindsNothingAsync(test);
}

[Fact]
public async Task WhenHasConstAsIdWithAlias()
{
var test = @"
[Dolittle.SDK.Events.EventType(alias: ""Bob"", eventTypeId: Id)]
class SomeEvent
{
const string Id = ""c6f87322-be67-4aaf-a9f4-fdc24ac4f0fb"";
public string Name {get; set;}
}";

await VerifyAnalyzerFindsNothingAsync(test);
}

[Fact]
public async Task ShouldDetectEventTypeWithInvalidNamedArguments()
Expand All @@ -116,7 +146,7 @@ class SomeEvent
{
Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(2, 2, 2, 80)
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "\"c6f87322-be67-4aaf\"")
.WithArguments("Dolittle.SDK.Events.EventType", "eventTypeId", "c6f87322-be67-4aaf")
};

await VerifyAnalyzerAsync(test, expected);
Expand Down Expand Up @@ -148,7 +178,7 @@ class SomeEvent
{
Diagnostic(DescriptorRules.InvalidIdentity)
.WithSpan(2, 2, 2, 47)
.WithArguments("Dolittle.SDK.Events.Handling.EventHandler", "eventHandlerId", "\"\""),
.WithArguments("Dolittle.SDK.Events.Handling.EventHandler", "eventHandlerId", ""),
};

await VerifyAnalyzerAsync(test, expected);
Expand Down

0 comments on commit eb7c367

Please sign in to comment.