From eb7c3679051818ab7fa82c0e7f8c7dac0c35423a Mon Sep 17 00:00:00 2001 From: Magne Helleborg Date: Fri, 18 Oct 2024 14:35:13 +0200 Subject: [PATCH] Let analyzers support constants for attribute identifiers --- Source/Analyzers/AttributeIdentityAnalyzer.cs | 36 ++++++++++++++++-- .../AnnotationIdentityCodeFixTests.cs | 14 +++---- .../Diagnostics/AnnotationAnalyzerTests.cs | 38 +++++++++++++++++-- 3 files changed, 74 insertions(+), 14 deletions(-) diff --git a/Source/Analyzers/AttributeIdentityAnalyzer.cs b/Source/Analyzers/AttributeIdentityAnalyzer.cs index 93eee1db..6fcf3567 100644 --- a/Source/Analyzers/AttributeIdentityAnalyzer.cs +++ b/Source/Analyzers/AttributeIdentityAnalyzer.cs @@ -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.Empty.Add("identityParameter", identityParameter.Name); context.ReportDiagnostic(Diagnostic.Create(DescriptorRules.InvalidIdentity, attribute.GetLocation(), properties, @@ -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())); diff --git a/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs b/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs index 666dca6c..9383176f 100644 --- a/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs +++ b/Tests/Analyzers/CodeFixes/AnnotationIdentityCodeFixTests.cs @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -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); } } diff --git a/Tests/Analyzers/Diagnostics/AnnotationAnalyzerTests.cs b/Tests/Analyzers/Diagnostics/AnnotationAnalyzerTests.cs index b485f06b..f62eb512 100644 --- a/Tests/Analyzers/Diagnostics/AnnotationAnalyzerTests.cs +++ b/Tests/Analyzers/Diagnostics/AnnotationAnalyzerTests.cs @@ -52,7 +52,7 @@ class SomeEvent { Diagnostic(DescriptorRules.InvalidIdentity) .WithSpan(4, 2, 4, 15) - .WithArguments("EventType", "eventTypeId", "\"\"") + .WithArguments("EventType", "eventTypeId", "") }; await VerifyAnalyzerAsync(test, expected); @@ -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); @@ -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() @@ -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); @@ -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);