From ce5200538d9c7a24b71ca6646484c7fb9dec94bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Mon, 13 Oct 2025 00:53:22 -0400 Subject: [PATCH 1/4] Added support for analyzing methods with LoggerMessage attributes in DoNotLogClassifiedDataAnalyzer and LoggerParameterTypeAnalyzer --- .../Rules/DoNotLogClassifiedDataAnalyzer.cs | 29 +- .../Rules/LoggerParameterTypeAnalyzer.cs | 76 +++++- .../DoNotLogClassifiedDataAnalyzerTests.cs | 132 ++++++++- .../Rules/LoggerParameterTypeAnalyzerTests.cs | 257 +++++++++++++++++- 4 files changed, 490 insertions(+), 4 deletions(-) diff --git a/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs index 1fd705f36..7141cb97b 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using Meziantou.Analyzer.Internals; @@ -34,6 +34,7 @@ public override void Initialize(AnalysisContext context) return; context.RegisterOperationAction(ctx.AnalyzeInvocationDeclaration, OperationKind.Invocation); + context.RegisterSymbolAction(ctx.AnalyzeMethodSymbol, SymbolKind.Method); }); } @@ -47,6 +48,7 @@ public AnalyzerContext(Compilation compilation) LoggerExtensionsSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerExtensions"); LoggerMessageSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessage"); + LoggerMessageAttributeSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessageAttribute"); StructuredLogFieldAttributeSymbol = compilation.GetBestTypeByMetadataName("Meziantou.Analyzer.Annotations.StructuredLogFieldAttribute"); DataClassificationAttributeSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute"); @@ -57,11 +59,36 @@ public AnalyzerContext(Compilation compilation) public INamedTypeSymbol? LoggerSymbol { get; } public INamedTypeSymbol? LoggerExtensionsSymbol { get; } public INamedTypeSymbol? LoggerMessageSymbol { get; } + public INamedTypeSymbol? LoggerMessageAttributeSymbol { get; } public INamedTypeSymbol? DataClassificationAttributeSymbol { get; } public bool IsValid => DataClassificationAttributeSymbol is not null && LoggerSymbol is not null; + public void AnalyzeMethodSymbol(SymbolAnalysisContext context) + { + var method = (IMethodSymbol)context.Symbol; + + // Check if method has LoggerMessageAttribute + if (!method.HasAttribute(LoggerMessageAttributeSymbol)) + return; + + // Validate each parameter + foreach (var parameter in method.Parameters) + { + // Skip the ILogger parameter + if (parameter.Type.IsEqualTo(LoggerSymbol)) + continue; + + // Check if parameter or its type has DataClassificationAttribute + if (parameter.HasAttribute(DataClassificationAttributeSymbol!, inherits: true) || + parameter.Type.HasAttribute(DataClassificationAttributeSymbol!, inherits: true)) + { + context.ReportDiagnostic(Rule, parameter); + } + } + } + public void AnalyzeInvocationDeclaration(OperationAnalysisContext context) { var operation = (IInvocationOperation)context.Operation; diff --git a/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs b/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs index c0a463b5a..7100536d4 100644 --- a/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; @@ -82,6 +82,7 @@ public override void Initialize(AnalysisContext context) return; context.RegisterOperationAction(ctx.AnalyzeInvocationDeclaration, OperationKind.Invocation); + context.RegisterSymbolAction(ctx.AnalyzeMethodSymbol, SymbolKind.Method); }); } @@ -104,6 +105,7 @@ public AnalyzerContext(CompilationStartAnalysisContext context) LoggerExtensionsSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerExtensions"); LoggerMessageSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessage"); + LoggerMessageAttributeSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessageAttribute"); StructuredLogFieldAttributeSymbol = compilation.GetBestTypeByMetadataName("Meziantou.Analyzer.Annotations.StructuredLogFieldAttribute"); SerilogLoggerEnrichmentConfigurationWithPropertySymbol = DocumentationCommentId.GetFirstSymbolForDeclarationId("M:Serilog.Configuration.LoggerEnrichmentConfiguration.WithProperty(System.String,System.Object,System.Boolean)", compilation); @@ -230,6 +232,7 @@ static Location CreateLocation(AdditionalText file, SourceText sourceText, TextL public INamedTypeSymbol? LoggerSymbol { get; } public INamedTypeSymbol? LoggerExtensionsSymbol { get; } public INamedTypeSymbol? LoggerMessageSymbol { get; } + public INamedTypeSymbol? LoggerMessageAttributeSymbol { get; } public INamedTypeSymbol? SerilogLogSymbol { get; } public INamedTypeSymbol? SerilogILoggerSymbol { get; } @@ -242,6 +245,68 @@ static Location CreateLocation(AdditionalText file, SourceText sourceText, TextL public bool IsValid => Configuration.Count > 0; + public void AnalyzeMethodSymbol(SymbolAnalysisContext context) + { + var method = (IMethodSymbol)context.Symbol; + + // Check if method has LoggerMessageAttribute + var loggerMessageAttribute = method.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.IsEqualTo(LoggerMessageAttributeSymbol)); + if (loggerMessageAttribute is null) + return; + + // Get the message format string from the attribute + string? formatString = null; + foreach (var arg in loggerMessageAttribute.ConstructorArguments) + { + if (arg.Type?.SpecialType == SpecialType.System_String && arg.Value is string str) + { + formatString = str; + break; + } + } + + if (string.IsNullOrEmpty(formatString)) + return; + + // Parse the format string to get template parameter names + var logFormat = new LogValuesFormatter(formatString); + if (logFormat.ValueNames.Count == 0) + return; + + // Create a dictionary mapping parameter names to their types + var parameterMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var parameter in method.Parameters) + { + // Skip the ILogger parameter + if (parameter.Type.IsEqualTo(LoggerSymbol)) + continue; + + parameterMap[parameter.Name] = (parameter.Type, parameter); + } + + // Validate each template parameter + foreach (var templateParamName in logFormat.ValueNames) + { + if (parameterMap.TryGetValue(templateParamName, out var paramInfo)) + { + // Validate the parameter type + ValidateParameterName(context, paramInfo.Parameter, templateParamName); + + if (!Configuration.IsValid(context.Compilation, templateParamName, paramInfo.Type, out var ruleFound)) + { + var expectedSymbols = Configuration.GetSymbols(templateParamName) ?? []; + var expectedSymbolsStr = $"must be of type {string.Join(" or ", expectedSymbols.Select(s => $"'{FormatType(s)}'"))} but is of type '{FormatType(paramInfo.Type)}'"; + context.ReportDiagnostic(Rule, paramInfo.Parameter, templateParamName, expectedSymbolsStr); + } + + if (!ruleFound) + { + context.ReportDiagnostic(RuleMissingConfiguration, paramInfo.Parameter, templateParamName); + } + } + } + } + public void AnalyzeInvocationDeclaration(OperationAnalysisContext context) { var operation = (IInvocationOperation)context.Operation; @@ -440,6 +505,15 @@ private void ValidateParameterName(OperationAnalysisContext context, IOperation } } + private void ValidateParameterName(SymbolAnalysisContext context, IParameterSymbol parameter, string name) + { + var expectedSymbols = Configuration.GetSymbols(name); + if (expectedSymbols is []) + { + context.ReportDiagnostic(Rule, parameter, name, "is not allowed by configuration"); + } + } + private static string RemovePrefix(string name, char[]? potentialNamePrefixes) { if (potentialNamePrefixes is not null) diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs index 0a84535a1..86a0082f6 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Meziantou.Analyzer.Rules; using Meziantou.Analyzer.Test.Helpers; using TestHelper; @@ -192,6 +192,136 @@ class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataCla { public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessage_NoDataClassification() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] + static partial void LogTestMessage(ILogger logger, string table21, float threshold42, [CallerMemberName] string method = ""); +} + +class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute +{ + public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessage_DataClassification_Parameter() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] + static partial void LogTestMessage(ILogger logger, [TaxonomyAttribute] string [|table21|], float threshold42, [CallerMemberName] string method = ""); +} + +class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute +{ + public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessage_DataClassification_ParameterType() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] + static partial void LogTestMessage(ILogger logger, ClassifiedData [|table21|], float threshold42, [CallerMemberName] string method = ""); +} + +[TaxonomyAttribute] +class ClassifiedData +{ + public string Value { get; set; } +} + +class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute +{ + public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessage_DataClassification_MultipleParameters() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] + static partial void LogTestMessage(ILogger logger, [TaxonomyAttribute] string [|table21|], [TaxonomyAttribute] float [|threshold42|], [CallerMemberName] string method = ""); +} + +class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute +{ + public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessage_DataClassification_SkipLoggerParameter() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message")] + static partial void LogTestMessage(ILogger logger); +} + +class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute +{ + public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } +} + +class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) diff --git a/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs index cf538ae59..1f3383a80 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Meziantou.Analyzer.Rules; using Meziantou.Analyzer.Test.Helpers; using TestHelper; @@ -475,6 +475,261 @@ await CreateProjectBuilder() .WithSourceCode(SourceCode) .AddAdditionalFile("LoggerParameterTypes.txt", """ Prop;System.Int32 +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_ValidParameterTypes() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] + static partial void LogTestMessage(ILogger logger, string Prop, int Name); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Prop;System.String +Name;System.Int32 +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_InvalidParameterType() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] + static partial void LogTestMessage(ILogger logger, int [|Prop|], string Name); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Prop;System.String +Name;System.String +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_MultipleInvalidParameterTypes() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] + static partial void LogTestMessage(ILogger logger, int [|Prop|], int [|Name|]); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Prop;System.String +Name;System.String +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_MissingConfiguration() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] + static partial void LogTestMessage(ILogger logger, string [|Prop|], int Name); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Name;System.Int32 +""") + .ShouldReportDiagnosticWithMessage("Log parameter 'Prop' has no configured type") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_DeniedParameter() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] + static partial void LogTestMessage(ILogger logger, string [|Prop|], int Name); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Name;System.Int32 +Prop; +""") + .ShouldReportDiagnosticWithMessage("Log parameter 'Prop' is not allowed by configuration") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_SkipILoggerParameter() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Name}")] + static partial void LogTestMessage(ILogger logger, int Name); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Name;System.Int32 +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_WithCallerMemberName() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; +using System.Runtime.CompilerServices; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message from {Method} with {Name}")] + static partial void LogTestMessage(ILogger logger, int Name, [CallerMemberName] string Method = ""); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Method;System.String +Name;System.Int32 +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_NullableParameterType() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test with {Value}")] + static partial void LogTestMessage(ILogger logger, int Value); + + [LoggerMessage(10_005, LogLevel.Trace, "Test with {Value}")] + static partial void LogTestMessage2(ILogger logger, int? Value); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Value;System.Nullable{System.Int32} +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_NoConfiguration() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop}")] + static partial void LogTestMessage(ILogger logger, string Prop); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_EmptyFormatString() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "")] + static partial void LogTestMessage(ILogger logger); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Name;System.Int32 +""") + .ValidateAsync(); + } + + [Fact] + public async Task LoggerMessageAttribute_NoFormatParameters() + { + const string SourceCode = """ +using Microsoft.Extensions.Logging; + +partial class LoggerExtensions +{ + [LoggerMessage(10_004, LogLevel.Trace, "Test message without parameters")] + static partial void LogTestMessage(ILogger logger); +} + +class Program { static void Main() { } } +"""; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .AddAdditionalFile("LoggerParameterTypes.txt", """ +Name;System.Int32 """) .ValidateAsync(); } From ff7d277f2ad72bd7e51bdb063f507d550cb8eaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Mon, 13 Oct 2025 11:29:45 -0400 Subject: [PATCH 2/4] improvement --- src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs b/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs index 7100536d4..dc57bac1b 100644 --- a/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/LoggerParameterTypeAnalyzer.cs @@ -250,7 +250,7 @@ public void AnalyzeMethodSymbol(SymbolAnalysisContext context) var method = (IMethodSymbol)context.Symbol; // Check if method has LoggerMessageAttribute - var loggerMessageAttribute = method.GetAttributes().FirstOrDefault(attr => attr.AttributeClass.IsEqualTo(LoggerMessageAttributeSymbol)); + var loggerMessageAttribute = method.GetAttribute(LoggerMessageAttributeSymbol); if (loggerMessageAttribute is null) return; @@ -258,7 +258,7 @@ public void AnalyzeMethodSymbol(SymbolAnalysisContext context) string? formatString = null; foreach (var arg in loggerMessageAttribute.ConstructorArguments) { - if (arg.Type?.SpecialType == SpecialType.System_String && arg.Value is string str) + if (arg.Type.IsString() && arg.Value is string str) { formatString = str; break; From b1f5587a630a1947665944767878a6be035cb59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Mon, 13 Oct 2025 11:34:11 -0400 Subject: [PATCH 3/4] Revert changes --- .../Rules/DoNotLogClassifiedDataAnalyzer.cs | 29 +--- .../DoNotLogClassifiedDataAnalyzerTests.cs | 132 +----------------- 2 files changed, 2 insertions(+), 159 deletions(-) diff --git a/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs index 7141cb97b..1fd705f36 100644 --- a/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DoNotLogClassifiedDataAnalyzer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using Meziantou.Analyzer.Internals; @@ -34,7 +34,6 @@ public override void Initialize(AnalysisContext context) return; context.RegisterOperationAction(ctx.AnalyzeInvocationDeclaration, OperationKind.Invocation); - context.RegisterSymbolAction(ctx.AnalyzeMethodSymbol, SymbolKind.Method); }); } @@ -48,7 +47,6 @@ public AnalyzerContext(Compilation compilation) LoggerExtensionsSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerExtensions"); LoggerMessageSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessage"); - LoggerMessageAttributeSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LoggerMessageAttribute"); StructuredLogFieldAttributeSymbol = compilation.GetBestTypeByMetadataName("Meziantou.Analyzer.Annotations.StructuredLogFieldAttribute"); DataClassificationAttributeSymbol = compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute"); @@ -59,36 +57,11 @@ public AnalyzerContext(Compilation compilation) public INamedTypeSymbol? LoggerSymbol { get; } public INamedTypeSymbol? LoggerExtensionsSymbol { get; } public INamedTypeSymbol? LoggerMessageSymbol { get; } - public INamedTypeSymbol? LoggerMessageAttributeSymbol { get; } public INamedTypeSymbol? DataClassificationAttributeSymbol { get; } public bool IsValid => DataClassificationAttributeSymbol is not null && LoggerSymbol is not null; - public void AnalyzeMethodSymbol(SymbolAnalysisContext context) - { - var method = (IMethodSymbol)context.Symbol; - - // Check if method has LoggerMessageAttribute - if (!method.HasAttribute(LoggerMessageAttributeSymbol)) - return; - - // Validate each parameter - foreach (var parameter in method.Parameters) - { - // Skip the ILogger parameter - if (parameter.Type.IsEqualTo(LoggerSymbol)) - continue; - - // Check if parameter or its type has DataClassificationAttribute - if (parameter.HasAttribute(DataClassificationAttributeSymbol!, inherits: true) || - parameter.Type.HasAttribute(DataClassificationAttributeSymbol!, inherits: true)) - { - context.ReportDiagnostic(Rule, parameter); - } - } - } - public void AnalyzeInvocationDeclaration(OperationAnalysisContext context) { var operation = (IInvocationOperation)context.Operation; diff --git a/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs index 86a0082f6..0a84535a1 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DoNotLogClassifiedDataAnalyzerTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Meziantou.Analyzer.Rules; using Meziantou.Analyzer.Test.Helpers; using TestHelper; @@ -192,136 +192,6 @@ class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataCla { public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } } -"""; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task LoggerMessage_NoDataClassification() - { - const string SourceCode = """ -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; - -partial class LoggerExtensions -{ - [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] - static partial void LogTestMessage(ILogger logger, string table21, float threshold42, [CallerMemberName] string method = ""); -} - -class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute -{ - public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } -} - -class Program { static void Main() { } } -"""; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task LoggerMessage_DataClassification_Parameter() - { - const string SourceCode = """ -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; - -partial class LoggerExtensions -{ - [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] - static partial void LogTestMessage(ILogger logger, [TaxonomyAttribute] string [|table21|], float threshold42, [CallerMemberName] string method = ""); -} - -class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute -{ - public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } -} - -class Program { static void Main() { } } -"""; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task LoggerMessage_DataClassification_ParameterType() - { - const string SourceCode = """ -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; - -partial class LoggerExtensions -{ - [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] - static partial void LogTestMessage(ILogger logger, ClassifiedData [|table21|], float threshold42, [CallerMemberName] string method = ""); -} - -[TaxonomyAttribute] -class ClassifiedData -{ - public string Value { get; set; } -} - -class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute -{ - public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } -} - -class Program { static void Main() { } } -"""; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task LoggerMessage_DataClassification_MultipleParameters() - { - const string SourceCode = """ -using Microsoft.Extensions.Logging; -using System.Runtime.CompilerServices; - -partial class LoggerExtensions -{ - [LoggerMessage(10_004, LogLevel.Trace, "Test message with parameters {Method} {Threshold42} {Table21}")] - static partial void LogTestMessage(ILogger logger, [TaxonomyAttribute] string [|table21|], [TaxonomyAttribute] float [|threshold42|], [CallerMemberName] string method = ""); -} - -class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute -{ - public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } -} - -class Program { static void Main() { } } -"""; - await CreateProjectBuilder() - .WithSourceCode(SourceCode) - .ValidateAsync(); - } - - [Fact] - public async Task LoggerMessage_DataClassification_SkipLoggerParameter() - { - const string SourceCode = """ -using Microsoft.Extensions.Logging; - -partial class LoggerExtensions -{ - [LoggerMessage(10_004, LogLevel.Trace, "Test message")] - static partial void LogTestMessage(ILogger logger); -} - -class TaxonomyAttribute : Microsoft.Extensions.Compliance.Classification.DataClassificationAttribute -{ - public TaxonomyAttribute() : base(Microsoft.Extensions.Compliance.Classification.DataClassification.Unknown) { } -} - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) From 04ae90ff18a94d4fdcc38f4a2df60c038e7dc7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Mon, 13 Oct 2025 11:38:45 -0400 Subject: [PATCH 4/4] update tests --- .../Rules/LoggerParameterTypeAnalyzerTests.cs | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs index 1f3383a80..bab9041d6 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/LoggerParameterTypeAnalyzerTests.cs @@ -515,11 +515,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] static partial void LogTestMessage(ILogger logger, int [|Prop|], string Name); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Prop;System.String Name;System.String @@ -539,11 +538,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] static partial void LogTestMessage(ILogger logger, int [|Prop|], int [|Name|]); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Prop;System.String Name;System.String @@ -563,11 +561,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] static partial void LogTestMessage(ILogger logger, string [|Prop|], int Name); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Name;System.Int32 """) @@ -587,11 +584,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop} and {Name}")] static partial void LogTestMessage(ILogger logger, string [|Prop|], int Name); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Name;System.Int32 Prop; @@ -611,11 +607,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Name}")] static partial void LogTestMessage(ILogger logger, int Name); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Name;System.Int32 """) @@ -634,11 +629,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message from {Method} with {Name}")] static partial void LogTestMessage(ILogger logger, int Name, [CallerMemberName] string Method = ""); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Method;System.String Name;System.Int32 @@ -660,11 +654,10 @@ partial class LoggerExtensions [LoggerMessage(10_005, LogLevel.Trace, "Test with {Value}")] static partial void LogTestMessage2(ILogger logger, int? Value); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Value;System.Nullable{System.Int32} """) @@ -682,11 +675,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message with {Prop}")] static partial void LogTestMessage(ILogger logger, string Prop); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .ValidateAsync(); } @@ -701,11 +693,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "")] static partial void LogTestMessage(ILogger logger); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Name;System.Int32 """) @@ -723,11 +714,10 @@ partial class LoggerExtensions [LoggerMessage(10_004, LogLevel.Trace, "Test message without parameters")] static partial void LogTestMessage(ILogger logger); } - -class Program { static void Main() { } } """; await CreateProjectBuilder() .WithSourceCode(SourceCode) + .WithOutputKind(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary) .AddAdditionalFile("LoggerParameterTypes.txt", """ Name;System.Int32 """)